diff --git a/.github/helper/roulette.py b/.github/helper/roulette.py index ba775d6794..ea4f07b9f7 100644 --- a/.github/helper/roulette.py +++ b/.github/helper/roulette.py @@ -18,7 +18,7 @@ def is_js(file): return file.endswith("js") def is_docs(file): - regex = re.compile('\.(md|png|jpg|jpeg)$|^.github|LICENSE') + regex = re.compile(r'\.(md|png|jpg|jpeg)$|^.github|LICENSE') return bool(regex.search(file)) diff --git a/.github/helper/semgrep_rules/ux.js b/.github/helper/semgrep_rules/ux.js new file mode 100644 index 0000000000..ae73f9cc60 --- /dev/null +++ b/.github/helper/semgrep_rules/ux.js @@ -0,0 +1,9 @@ + +// ok: frappe-missing-translate-function-js +frappe.msgprint('{{ _("Both login and password required") }}'); + +// ruleid: frappe-missing-translate-function-js +frappe.msgprint('What'); + +// ok: frappe-missing-translate-function-js +frappe.throw(' {{ _("Both login and password required") }}. '); diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py index 4a74457435..a00d3cd8ae 100644 --- a/.github/helper/semgrep_rules/ux.py +++ b/.github/helper/semgrep_rules/ux.py @@ -2,30 +2,30 @@ import frappe from frappe import msgprint, throw, _ -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python throw("Error Occured") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python frappe.throw("Error Occured") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python frappe.msgprint("Useful message") -# ruleid: frappe-missing-translate-function +# ruleid: frappe-missing-translate-function-python msgprint("Useful message") -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python translatedmessage = _("Hello") -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python throw(translatedmessage) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python msgprint(translatedmessage) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python msgprint(_("Helpful message")) -# ok: frappe-missing-translate-function +# ok: frappe-missing-translate-function-python frappe.throw(_("Error occured")) diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml index ed06a6a80c..dd667f36c0 100644 --- a/.github/helper/semgrep_rules/ux.yml +++ b/.github/helper/semgrep_rules/ux.yml @@ -1,15 +1,30 @@ rules: -- id: frappe-missing-translate-function +- id: frappe-missing-translate-function-python pattern-either: - patterns: - pattern: frappe.msgprint("...", ...) - pattern-not: frappe.msgprint(_("..."), ...) - - pattern-not: frappe.msgprint(__("..."), ...) - patterns: - pattern: frappe.throw("...", ...) - pattern-not: frappe.throw(_("..."), ...) - - pattern-not: frappe.throw(__("..."), ...) message: | All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations - languages: [python, javascript, json] + languages: [python] + severity: ERROR + +- id: frappe-missing-translate-function-js + pattern-either: + - patterns: + - pattern: frappe.msgprint("...", ...) + - pattern-not: frappe.msgprint(__("..."), ...) + # ignore microtemplating e.g. msgprint("{{ _("server side translation") }}") + - pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...) + - patterns: + - pattern: frappe.throw("...", ...) + - pattern-not: frappe.throw(__("..."), ...) + # ignore microtemplating + - pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...) + message: | + All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations + languages: [javascript] severity: ERROR diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml new file mode 100644 index 0000000000..e8627a01fb --- /dev/null +++ b/.github/workflows/patch-mariadb-tests.yml @@ -0,0 +1,83 @@ +name: Patch + +on: [pull_request, workflow_dispatch] + +jobs: + test: + runs-on: ubuntu-18.04 + + name: Patch Test + + services: + mysql: + image: mariadb:10.3 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: YES + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 + + steps: + - name: Clone + uses: actions/checkout@v2 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.7 + + - name: Add to Hosts + run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}- + + - name: Cache node modules + uses: actions/cache@v2 + env: + cache-name: cache-node-modules + with: + path: ~/.npm + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install Dependencies + run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh + env: + BEFORE: ${{ env.GITHUB_EVENT_PATH.before }} + AFTER: ${{ env.GITHUB_EVENT_PATH.after }} + TYPE: server + + - name: Install + run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + DB: mariadb + TYPE: server + + - name: Run Patch Tests + run: | + cd ~/frappe-bench/ + wget https://frappeframework.com/files/v10-frappe.sql.gz + bench --site test_site --force restore ~/frappe-bench/v10-frappe.sql.gz + bench --site test_site migrate diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml index 1742e813c6..2476102e3d 100644 --- a/.github/workflows/server-mariadb-tests.yml +++ b/.github/workflows/server-mariadb-tests.yml @@ -3,6 +3,8 @@ name: Server on: pull_request: workflow_dispatch: + push: + branches: [ develop ] jobs: test: @@ -89,7 +91,6 @@ jobs: DB: mariadb TYPE: server - - name: Run Tests run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --use-orchestrator --with-coverage env: diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index d9ccb07da0..f342c0709e 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -3,6 +3,8 @@ name: UI on: pull_request: workflow_dispatch: + push: + branches: [ develop ] jobs: test: @@ -103,3 +105,5 @@ jobs: - name: UI Tests run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests frappe --headless --parallel --ci-build-id $GITHUB_RUN_ID + env: + CYPRESS_RECORD_KEY: 4a48f41c-11b3-425b-aa88-c58048fa69eb diff --git a/CODEOWNERS b/CODEOWNERS index 92723ab035..2dff157294 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -4,13 +4,10 @@ # the repo. Unless a later match takes precedence, * @frappe/frappe-review-team -website/ @prssanna -web_form/ @prssanna templates/ @surajshetty3416 www/ @surajshetty3416 integrations/ @leela patches/ @surajshetty3416 -dashboard/ @prssanna email/ @leela event_streaming/ @ruchamahabal data_import* @netchampfaris diff --git a/README.md b/README.md index e00bea7857..11343a632a 100644 --- a/README.md +++ b/README.md @@ -14,18 +14,21 @@
- - - - - - + + + + + + + + + - - - + + +
diff --git a/cypress/integration/form.js b/cypress/integration/form.js index 20ed7a61cd..909955c1df 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -18,6 +18,7 @@ context('Form', () => { cy.get('.primary-action').click(); cy.wait('@form_save').its('response.statusCode').should('eq', 200); cy.visit('/app/todo'); + cy.wait(300); cy.get('.title-text').should('be.visible').and('contain', 'To Do'); cy.get('.list-row').should('contain', 'this is a test todo'); }); diff --git a/cypress/integration/form_tour.js b/cypress/integration/form_tour.js new file mode 100644 index 0000000000..d12be63f3b --- /dev/null +++ b/cypress/integration/form_tour.js @@ -0,0 +1,88 @@ +context('Form Tour', () => { + before(() => { + cy.login(); + cy.visit('/app/form-tour'); + return cy.window().its('frappe').then(frappe => { + return frappe.call("frappe.tests.ui_test_helpers.create_form_tour"); + }); + }); + + const open_test_form_tour = () => { + cy.visit('/app/form-tour/Test Form Tour'); + cy.get('button[data-label="Show%20Tour"]').should('be.visible').and('contain', 'Show Tour').as('show_tour'); + cy.get('@show_tour').click(); + cy.wait(500); + cy.url().should('include', '/app/contact'); + }; + + it('jump to a form tour', open_test_form_tour); + + it('navigates a form tour', () => { + open_test_form_tour(); + + cy.get('#driver-popover-item').should('be.visible'); + cy.get('.frappe-control[data-fieldname="first_name"]').as('first_name'); + cy.get('@first_name').should('have.class', 'driver-highlighted-element'); + cy.get('.driver-next-btn').as('next_btn'); + + // next btn shouldn't move to next step, if first name is not entered + cy.get('@next_btn').click(); + cy.wait(500); + cy.get('@first_name').should('have.class', 'driver-highlighted-element'); + + // after filling the field, next step should be highlighted + cy.fill_field('first_name', 'Test Name', 'Data'); + cy.wait(500); + cy.get('@next_btn').click(); + cy.wait(500); + + // assert field is highlighted + cy.get('.frappe-control[data-fieldname="last_name"]').as('last_name'); + cy.get('@last_name').should('have.class', 'driver-highlighted-element'); + + // after filling the field, next step should be highlighted + cy.fill_field('last_name', 'Test Last Name', 'Data'); + cy.wait(500); + cy.get('@next_btn').click(); + cy.wait(500); + + // assert field is highlighted + cy.get('.frappe-control[data-fieldname="phone_nos"]').as('phone_nos'); + cy.get('@phone_nos').should('have.class', 'driver-highlighted-element'); + + // move to next step + cy.wait(500); + cy.get('@next_btn').click(); + cy.wait(500); + + // assert add row btn is highlighted + cy.get('@phone_nos').find('.grid-add-row').as('add_row'); + cy.get('@add_row').should('have.class', 'driver-highlighted-element'); + + // add a row & move to next step + cy.wait(500); + cy.get('@add_row').click(); + cy.wait(500); + + // assert table field is highlighted + cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as('phone'); + cy.get('@phone').should('have.class', 'driver-highlighted-element'); + // enter value in a table field + cy.fill_table_field('phone_nos', '1', 'phone', '1234567890'); + + // move to collapse row step + cy.wait(500); + cy.get('@next_btn').click(); + cy.wait(500); + + // collapse row + cy.get('.grid-row-open .grid-collapse-row').click(); + cy.wait(500); + + // assert save btn is highlighted + cy.get('.primary-action').should('have.class', 'driver-highlighted-element'); + cy.get('@next_btn').should('contain', 'Save'); + + }); +}); + \ No newline at end of file diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js index ecf0d49511..efa1959969 100644 --- a/esbuild/esbuild.js +++ b/esbuild/esbuild.js @@ -258,12 +258,17 @@ function get_watch_config() { async function clean_dist_folders(apps) { for (let app of apps) { let public_path = get_public_path(app); - await fs.promises.rmdir(path.resolve(public_path, "dist", "js"), { - recursive: true - }); - await fs.promises.rmdir(path.resolve(public_path, "dist", "css"), { - recursive: true - }); + let paths = [ + path.resolve(public_path, "dist", "js"), + path.resolve(public_path, "dist", "css") + ]; + for (let target of paths) { + if (fs.existsSync(target)) { + // rmdir is deprecated in node 16, this will work in both node 14 and 16 + let rmdir = fs.promises.rm || fs.promises.rmdir; + await rmdir(target, { recursive: true }); + } + } } } @@ -343,12 +348,7 @@ async function write_assets_json(metafile) { } } - let assets_json_path = path.resolve( - assets_path, - "frappe", - "dist", - "assets.json" - ); + let assets_json_path = path.resolve(assets_path, "assets.json"); let assets_json; try { assets_json = await fs.promises.readFile(assets_json_path, "utf-8"); diff --git a/frappe/__init__.py b/frappe/__init__.py index 9b208f7c2d..1c978945c7 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -21,7 +21,6 @@ if _dev_server: from werkzeug.local import Local, release_local import sys, importlib, inspect, json import typing -from past.builtins import cmp import click # Local application imports @@ -528,16 +527,20 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message if not delayed: now = True - from frappe.email import queue - queue.send(recipients=recipients, sender=sender, + from frappe.email.doctype.email_queue.email_queue import QueueBuilder + builder = QueueBuilder(recipients=recipients, sender=sender, subject=subject, message=message, text_content=text_content, reference_doctype = doctype or reference_doctype, reference_name = name or reference_name, add_unsubscribe_link=add_unsubscribe_link, unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message, attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to, send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority, queue_separately=queue_separately, - communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification, + communication=communication, read_receipt=read_receipt, is_notification=is_notification, inline_images=inline_images, header=header, print_letterhead=print_letterhead, with_container=with_container) + # build email queue and send the email if send_now is True. + builder.process(send_now=now) + + whitelisted = [] guest_methods = [] xss_safe_methods = [] @@ -1107,9 +1110,7 @@ def setup_module_map(): if not (local.app_modules and local.module_app): local.module_app, local.app_modules = {}, {} - for app in get_all_apps(True): - if app == "webnotes": - app = "frappe" + for app in get_all_apps(with_internal_apps=True): local.app_modules.setdefault(app, []) for module in get_module_list(app): module = scrub(module) @@ -1490,7 +1491,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, :param style: Print Format style. :param as_pdf: Return as PDF. Default False. :param password: Password to encrypt the pdf with. Default None""" - from frappe.website.render import build_page + from frappe.website.serve import get_response_content from frappe.utils.pdf import get_pdf local.form_dict.doctype = doctype @@ -1505,7 +1506,7 @@ def get_print(doctype=None, name=None, print_format=None, style=None, options = {'password': password} if not html: - html = build_page("printview") + html = get_response_content("printview") if as_pdf: return get_pdf(html, output = output, options = options) @@ -1682,7 +1683,7 @@ def get_desk_link(doctype, name): ) def bold(text): - return '{0}'.format(text) + return '{0}'.format(text) def safe_eval(code, eval_globals=None, eval_locals=None): '''A safer `eval`''' @@ -1693,6 +1694,23 @@ def safe_eval(code, eval_globals=None, eval_locals=None): "round": round } + UNSAFE_ATTRIBUTES = { + # Generator Attributes + "gi_frame", "gi_code", + # Coroutine Attributes + "cr_frame", "cr_code", "cr_origin", + # Async Generator Attributes + "ag_code", "ag_frame", + # Traceback Attributes + "tb_frame", "tb_next", + # Format Attributes + "format", "format_map", + } + + for attribute in UNSAFE_ATTRIBUTES: + if attribute in code: + throw('Illegal rule {0}. Cannot use "{1}"'.format(bold(code), attribute)) + if '__' in code: throw('Illegal rule {0}. Cannot use "__"'.format(bold(code))) diff --git a/frappe/api.py b/frappe/api.py index 9039ae0e5f..36d51e894c 100644 --- a/frappe/api.py +++ b/frappe/api.py @@ -1,6 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - import base64 import binascii import json @@ -11,6 +10,7 @@ import frappe.client import frappe.handler from frappe import _ from frappe.utils.response import build_response +from frappe.utils.data import sbool def handle(): @@ -108,25 +108,40 @@ def handle(): elif doctype: if frappe.local.request.method == "GET": - if frappe.local.form_dict.get('fields'): - frappe.local.form_dict['fields'] = json.loads(frappe.local.form_dict['fields']) - frappe.local.form_dict.setdefault('limit_page_length', 20) - frappe.local.response.update({ - "data": frappe.call( - frappe.client.get_list, - doctype, - **frappe.local.form_dict - ) - }) + # set fields for frappe.get_list + if frappe.local.form_dict.get("fields"): + frappe.local.form_dict["fields"] = json.loads(frappe.local.form_dict["fields"]) + + # set limit of records for frappe.get_list + frappe.local.form_dict.setdefault( + "limit_page_length", + frappe.local.form_dict.limit or frappe.local.form_dict.limit_page_length or 20, + ) + + # convert strings to native types - only as_dict and debug accept bool + for param in ["as_dict", "debug"]: + param_val = frappe.local.form_dict.get(param) + if param_val is not None: + frappe.local.form_dict[param] = sbool(param_val) + + # evaluate frappe.get_list + data = frappe.call(frappe.client.get_list, doctype, **frappe.local.form_dict) + + # set frappe.get_list result to response + frappe.local.response.update({"data": data}) if frappe.local.request.method == "POST": + # fetch data from from dict data = get_request_form_data() - data.update({ - "doctype": doctype - }) - frappe.local.response.update({ - "data": frappe.get_doc(data).insert().as_dict() - }) + data.update({"doctype": doctype}) + + # insert document from request data + doc = frappe.get_doc(data).insert() + + # set response data + frappe.local.response.update({"data": doc.as_dict()}) + + # commit for POST requests frappe.db.commit() else: raise frappe.DoesNotExistError diff --git a/frappe/app.py b/frappe/app.py index 64befdf531..920628dda4 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import os -from six import iteritems import logging from werkzeug.local import LocalManager @@ -18,9 +16,9 @@ import frappe.handler import frappe.auth import frappe.api import frappe.utils.response -import frappe.website.render from frappe.utils import get_site_name, sanitize_html from frappe.middlewares import StaticDataMiddleware +from frappe.website.serve import get_response from frappe.utils.error import make_error_snapshot from frappe.core.doctype.comment.comment import update_comments_in_parent_after_request from frappe import _ @@ -74,7 +72,7 @@ def application(request): response = frappe.utils.response.download_private_file(request.path) elif request.method in ('GET', 'HEAD', 'POST'): - response = frappe.website.render.render() + response = get_response() else: raise NotFound @@ -191,8 +189,9 @@ def make_form_dict(request): frappe.throw(_("Invalid request arguments")) try: - frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \ - for k, v in iteritems(args) }) + frappe.local.form_dict = frappe._dict({ + k: v[0] if isinstance(v, (list, tuple)) else v for k, v in args.items() + }) except IndexError: frappe.local.form_dict = frappe._dict(args) @@ -267,8 +266,7 @@ def handle_exception(e): make_error_snapshot(e) if return_as_message: - response = frappe.website.render.render("message", - http_status_code=http_status_code) + response = get_response("message", http_status_code=http_status_code) return response diff --git a/frappe/auth.py b/frappe/auth.py index 73cb8e8c15..ef79d96ddb 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -1,9 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals import datetime - from frappe import _ import frappe import frappe.database @@ -19,8 +16,7 @@ from frappe.core.doctype.activity_log.activity_log import add_authentication_log from frappe.twofactor import (should_run_2fa, authenticate_for_2factor, confirm_otp_token, get_cached_user_pass) from frappe.website.utils import get_home_page - -from six.moves.urllib.parse import quote +from urllib.parse import quote class HTTPRequest: diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index c673d5ceeb..ef579aca01 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -2,8 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from frappe.model.document import Document from frappe.desk.form import assign_to diff --git a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py index cb1e0ff8f4..e287b83965 100644 --- a/frappe/automation/doctype/assignment_rule/test_assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/test_assignment_rule.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.utils import random_string diff --git a/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py b/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py index 27f9aa40e1..c734495c39 100644 --- a/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py +++ b/frappe/automation/doctype/assignment_rule_day/assignment_rule_day.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py b/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py index ee8081c6d8..4d65efd5c1 100644 --- a/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py +++ b/frappe/automation/doctype/assignment_rule_user/assignment_rule_user.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index bf05baf5b6..d2afda1553 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from datetime import timedelta @@ -334,7 +333,7 @@ class AutoRepeat(Document): if self.reference_doctype and self.reference_document: res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id']) res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id']) - email_ids = list(set([d.email_id for d in res])) + email_ids = {d.email_id for d in res} if not email_ids: frappe.msgprint(_('No contacts linked to document'), alert=True) else: diff --git a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py index 6ceb4dba72..567c1161af 100644 --- a/frappe/automation/doctype/auto_repeat/test_auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/test_auto_repeat.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe diff --git a/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py b/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py index 3a7ced1370..8af3284cde 100644 --- a/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py +++ b/frappe/automation/doctype/auto_repeat_day/auto_repeat_day.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/automation/doctype/milestone/milestone.py b/frappe/automation/doctype/milestone/milestone.py index 64c073a378..6ea6d7544a 100644 --- a/frappe/automation/doctype/milestone/milestone.py +++ b/frappe/automation/doctype/milestone/milestone.py @@ -2,8 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from frappe.model.document import Document diff --git a/frappe/automation/doctype/milestone/test_milestone.py b/frappe/automation/doctype/milestone/test_milestone.py index 75602d48db..175c56e552 100644 --- a/frappe/automation/doctype/milestone/test_milestone.py +++ b/frappe/automation/doctype/milestone/test_milestone.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - #import frappe import unittest diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py index 388620bfb4..125cad7fa8 100644 --- a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py +++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py @@ -2,8 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from frappe.model.document import Document import frappe.cache_manager diff --git a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py index 05db3b025e..21b2779018 100644 --- a/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py +++ b/frappe/automation/doctype/milestone_tracker/test_milestone_tracker.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import frappe.cache_manager import unittest diff --git a/frappe/boot.py b/frappe/boot.py index 0dfcb8d1b4..0589e32ac8 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -1,10 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals - -from six import iteritems, text_type - """ bootstrap client session """ @@ -75,7 +70,7 @@ def get_bootinfo(): frappe.get_attr(method)(bootinfo) if bootinfo.lang: - bootinfo.lang = text_type(bootinfo.lang) + bootinfo.lang = str(bootinfo.lang) bootinfo.versions = {k: v['version'] for k, v in get_versions().items()} bootinfo.error_report_email = frappe.conf.error_report_email @@ -220,7 +215,7 @@ def load_translations(bootinfo): messages[name] = frappe._(name) # only untranslated - messages = {k:v for k, v in iteritems(messages) if k!=v} + messages = {k: v for k, v in messages.items() if k!=v} bootinfo["__messages"] = messages diff --git a/frappe/build.py b/frappe/build.py index c970ae3a28..ed19574cfd 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -1,11 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - import os import re import json import shutil import subprocess +from io import StringIO from tempfile import mkdtemp, mktemp from distutils.spawn import find_executable @@ -50,7 +50,7 @@ def build_missing_files(): development = frappe.local.conf.developer_mode or frappe.local.dev_server build_mode = "development" if development else "production" - assets_json = frappe.read_file(frappe.get_app_path('frappe', 'public', 'dist', 'assets.json')) + assets_json = frappe.read_file("assets/assets.json") if assets_json: assets_json = frappe.parse_json(assets_json) @@ -402,8 +402,6 @@ def get_build_maps(): def pack(target, sources, no_compress, verbose): - from six import StringIO - outtype, outtxt = target.split(".")[-1], "" jsm = JavascriptMinify() diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 7330c83102..9f09f26be8 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -1,8 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, json from frappe.model.document import Document from frappe.desk.notifications import (delete_notification_count_for, @@ -55,7 +53,7 @@ def clear_domain_cache(user=None): cache.delete_value(domain_cache_keys) def clear_global_cache(): - from frappe.website.render import clear_cache as clear_website_cache + from frappe.website.utils import clear_website_cache clear_doctype_cache() clear_website_cache() diff --git a/frappe/chat/__init__.py b/frappe/chat/__init__.py index dea0030839..4c9b1c5db7 100644 --- a/frappe/chat/__init__.py +++ b/frappe/chat/__init__.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe import _ diff --git a/frappe/chat/doctype/chat_message/chat_message.py b/frappe/chat/doctype/chat_message/chat_message.py index 5549aaa657..bc470a5e9c 100644 --- a/frappe/chat/doctype/chat_message/chat_message.py +++ b/frappe/chat/doctype/chat_message/chat_message.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - standard imports import json diff --git a/frappe/chat/doctype/chat_profile/chat_profile.py b/frappe/chat/doctype/chat_profile/chat_profile.py index 698d992d35..da10a836c4 100644 --- a/frappe/chat/doctype/chat_profile/chat_profile.py +++ b/frappe/chat/doctype/chat_profile/chat_profile.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - module imports from frappe.model.document import Document from frappe import _ diff --git a/frappe/chat/doctype/chat_room/chat_room.py b/frappe/chat/doctype/chat_room/chat_room.py index 609acaef7d..bdbee44d7a 100644 --- a/frappe/chat/doctype/chat_room/chat_room.py +++ b/frappe/chat/doctype/chat_room/chat_room.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - module imports from frappe.model.document import Document from frappe import _ diff --git a/frappe/chat/doctype/chat_room_user/chat_room_user.py b/frappe/chat/doctype/chat_room_user/chat_room_user.py index f8e13add82..f6dbdc7659 100644 --- a/frappe/chat/doctype/chat_room_user/chat_room_user.py +++ b/frappe/chat/doctype/chat_room_user/chat_room_user.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - module imports from frappe.model.document import Document import frappe diff --git a/frappe/chat/doctype/chat_token/chat_token.py b/frappe/chat/doctype/chat_token/chat_token.py index 30a76ef5bd..63d69a58be 100644 --- a/frappe/chat/doctype/chat_token/chat_token.py +++ b/frappe/chat/doctype/chat_token/chat_token.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/chat/util/__init__.py b/frappe/chat/util/__init__.py index 15977af566..383df581cd 100644 --- a/frappe/chat/util/__init__.py +++ b/frappe/chat/util/__init__.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - module imports from frappe.chat.util.util import ( get_user_doc, diff --git a/frappe/chat/util/test_util.py b/frappe/chat/util/test_util.py index 6d44a63d31..e2d05a4024 100644 --- a/frappe/chat/util/test_util.py +++ b/frappe/chat/util/test_util.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - standard imports import unittest @@ -9,7 +7,6 @@ from frappe.chat.util import ( safe_json_loads ) import frappe -import six class TestChatUtil(unittest.TestCase): def test_safe_json_loads(self): @@ -20,7 +17,7 @@ class TestChatUtil(unittest.TestCase): self.assertEqual(type(number), float) string = safe_json_loads("foobar") - self.assertEqual(type(string), six.text_type) + self.assertEqual(type(string), str) array = safe_json_loads('[{ "foo": "bar" }]') self.assertEqual(type(array), list) diff --git a/frappe/chat/util/util.py b/frappe/chat/util/util.py index 82df6dd127..b7e7991c2b 100644 --- a/frappe/chat/util/util.py +++ b/frappe/chat/util/util.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - # imports - standard imports import json from collections.abc import MutableMapping, MutableSequence, Sequence diff --git a/frappe/chat/website/__init__.py b/frappe/chat/website/__init__.py index f33f531cbf..12affd2782 100644 --- a/frappe/chat/website/__init__.py +++ b/frappe/chat/website/__init__.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.chat.util import filter_dict, safe_json_loads diff --git a/frappe/client.py b/frappe/client.py index a2e04452ff..66c457e893 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals import frappe from frappe import _ import frappe.model @@ -11,7 +9,6 @@ from frappe.utils import get_safe_filters from frappe.desk.reportview import validate_args from frappe.model.db_query import check_parent_permission -from six import iteritems, string_types, integer_types ''' Handle RESTful requests that are mapped to the `/api/resource` route. @@ -86,7 +83,7 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError) filters = get_safe_filters(filters) - if isinstance(filters, string_types): + if isinstance(filters, str): filters = {"name": filters} try: @@ -135,7 +132,7 @@ def set_value(doctype, name, fieldname, value=None): if not value: values = fieldname - if isinstance(fieldname, string_types): + if isinstance(fieldname, str): try: values = json.loads(fieldname) except ValueError: @@ -161,7 +158,7 @@ def insert(doc=None): '''Insert a document :param doc: JSON or dict object to be inserted''' - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) if doc.get("parent") and doc.get("parenttype"): @@ -179,7 +176,7 @@ def insert_many(docs=None): '''Insert multiple documents :param docs: JSON or list of dict objects to be inserted in one request''' - if isinstance(docs, string_types): + if isinstance(docs, str): docs = json.loads(docs) out = [] @@ -205,7 +202,7 @@ def save(doc): '''Update (save) an existing document :param doc: JSON or dict object with the properties of the document to be updated''' - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) doc = frappe.get_doc(doc) @@ -228,7 +225,7 @@ def submit(doc): '''Submit a document :param doc: JSON or dict object to be submitted remotely''' - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) doc = frappe.get_doc(doc) @@ -266,7 +263,7 @@ def make_width_property_setter(doc): '''Set width Property Setter :param doc: Property Setter document with `width` property''' - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) if doc["doctype"]=="Property Setter" and doc["property"]=="width": frappe.get_doc(doc).insert(ignore_permissions = True) @@ -280,7 +277,7 @@ def bulk_update(docs): failed_docs = [] for doc in docs: try: - ddoc = {key: val for key, val in iteritems(doc) if key not in ['doctype', 'docname']} + ddoc = {key: val for key, val in doc.items() if key not in ['doctype', 'docname']} doctype = doc['doctype'] docname = doc['docname'] doc = frappe.get_doc(doctype, docname) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index e521acc9ad..be9d107025 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals, absolute_import, print_function import sys import click import cProfile @@ -10,7 +9,7 @@ import frappe import frappe.utils import subprocess # nosec from functools import wraps -from six import StringIO +from io import StringIO from os import environ click.disable_unicode_literals_warning = True diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index e9638800cd..d69ebb3024 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals, absolute_import, print_function import click import sys import frappe diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index 48a7fd1db7..68d210eaaa 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals, absolute_import, print_function import click from frappe.commands import pass_context, get_site from frappe.exceptions import SiteNotSpecifiedError diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 4da0f6bb78..b6e48e3388 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -69,14 +69,14 @@ def watch(apps=None): def clear_cache(context): "Clear cache, doctype cache and defaults" import frappe.sessions - import frappe.website.render + from frappe.website.utils import clear_website_cache from frappe.desk.notifications import clear_notifications for site in context.sites: try: frappe.connect(site) frappe.clear_cache() clear_notifications() - frappe.website.render.clear_cache() + clear_website_cache() finally: frappe.destroy() if not context.sites: @@ -86,12 +86,12 @@ def clear_cache(context): @pass_context def clear_website_cache(context): "Clear website cache" - import frappe.website.render + from frappe.website.utils import clear_website_cache for site in context.sites: try: frappe.init(site=site) frappe.connect() - frappe.website.render.clear_cache() + clear_website_cache() finally: frappe.destroy() if not context.sites: @@ -222,7 +222,7 @@ def execute(context, method, args=None, kwargs=None, profile=False): if profile: import pstats - from six import StringIO + from io import StringIO pr.disable() s = StringIO() @@ -572,22 +572,29 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), profile=Fal # Generate coverage report only for app that is being tested source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe') - omit=[ - '*.html', + incl = [ + '*.py', + ] + omit = [ '*.js', '*.xml', + '*.pyc', '*.css', '*.less', '*.scss', '*.vue', + '*.html', + '*/test_*', + '*/node_modules/*', '*/doctype/*/*_dashboard.py', - '*/patches/*' + '*/patches/*', ] if not app or app == 'frappe': + omit.append('*/tests/*') omit.append('*/commands/*') - cov = Coverage(source=[source_path], omit=omit) + cov = Coverage(source=[source_path], omit=omit, include=incl) cov.start() ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests, @@ -654,7 +661,7 @@ def run_ui_tests(context, app, headless=False, parallel=True, ci_build_id=None): frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile") # run for headless mode - run_or_open = 'run --browser firefox --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open' + run_or_open = 'run --browser firefox --record' if headless else 'open' command = '{site_env} {password_env} {cypress} {run_or_open}' formatted_command = command.format(site_env=site_env, password_env=password_env, cypress=cypress_path, run_or_open=run_or_open) diff --git a/frappe/config/__init__.py b/frappe/config/__init__.py index 30be82d0df..62a877be24 100644 --- a/frappe/config/__init__.py +++ b/frappe/config/__init__.py @@ -1,6 +1,3 @@ -from __future__ import unicode_literals -import json -from six import iteritems import frappe from frappe import _ from frappe.desk.moduleview import (get_data, get_onboard_items, config_exists, get_module_link_items_from_list) diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 3ca9547188..77305168c1 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -1,7 +1,6 @@ # 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 _ @@ -154,7 +153,7 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil doctypes = frappe.db.get_all("DocField", filters=filters, fields=["parent"], distinct=True, as_list=True) - doctypes = tuple([d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)]) + doctypes = tuple(d for d in doctypes if re.search(txt+".*", _(d[0]), re.IGNORECASE)) filters.update({ "dt": ("not in", [d[0] for d in doctypes]) diff --git a/frappe/contacts/doctype/address/address.py b/frappe/contacts/doctype/address/address.py index 84b925d50e..755bc63064 100644 --- a/frappe/contacts/doctype/address/address.py +++ b/frappe/contacts/doctype/address/address.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import throw, _ @@ -10,15 +9,10 @@ from frappe.utils import cstr from frappe.model.document import Document from jinja2 import TemplateSyntaxError -from frappe.utils.user import is_website_user from frappe.model.naming import make_autoname from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links -from six import iteritems, string_types -from past.builtins import cmp from frappe.contacts.address_and_contact import set_link_title -import functools - class Address(Document): def __setup__(self): @@ -112,10 +106,13 @@ def get_default_address(doctype, name, sort_key='is_primary_address'): WHERE dl.parent = addr.name and dl.link_doctype = %s and dl.link_name = %s and ifnull(addr.disabled, 0) = 0 - """ %(sort_key, '%s', '%s'), (doctype, name)) + """ %(sort_key, '%s', '%s'), (doctype, name), as_dict=True) if out: - return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] + for contact in out: + if contact.get(sort_key): + return contact.name + return out[0].name else: return None @@ -141,7 +138,7 @@ def get_territory_from_address(address): if not address: return - if isinstance(address, string_types): + if isinstance(address, str): address = frappe.get_cached_doc("Address", address) territory = None @@ -174,14 +171,11 @@ def get_address_list(doctype, txt, filters, limit_start, limit_page_length = 20, def has_website_permission(doc, ptype, user, verbose=False): """Returns true if there is a related lead or contact related to this document""" contact_name = frappe.db.get_value("Contact", {"email_id": frappe.session.user}) + if contact_name: contact = frappe.get_doc('Contact', contact_name) return contact.has_common_link(doc) - lead_name = frappe.db.get_value("Lead", {"email_id": frappe.session.user}) - if lead_name: - return doc.has_link('Lead', lead_name) - return False def get_address_templates(address): @@ -214,7 +208,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): condition = "" meta = frappe.get_meta("Address") - for fieldname, value in iteritems(filters): + for fieldname, value in filters.items(): if meta.get_field(fieldname) or fieldname in frappe.db.DEFAULT_COLUMNS: condition += " and {field}={value}".format( field=fieldname, @@ -263,7 +257,7 @@ def address_query(doctype, txt, searchfield, start, page_len, filters): def get_condensed_address(doc): fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country"] - return ", ".join([doc.get(d) for d in fields if doc.get(d)]) + return ", ".join(doc.get(d) for d in fields if doc.get(d)) def update_preferred_address(address, field): frappe.db.set_value('Address', address, field, 0) diff --git a/frappe/contacts/doctype/address/test_address.py b/frappe/contacts/doctype/address/test_address.py index d6d4e50491..ed61b6f0ee 100644 --- a/frappe/contacts/doctype/address/test_address.py +++ b/frappe/contacts/doctype/address/test_address.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe, unittest from frappe.contacts.doctype.address.address import get_address_display diff --git a/frappe/contacts/doctype/address_template/address_template.py b/frappe/contacts/doctype/address_template/address_template.py index 2ca9aebff5..2d69a792ab 100644 --- a/frappe/contacts/doctype/address_template/address_template.py +++ b/frappe/contacts/doctype/address_template/address_template.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils import cint diff --git a/frappe/contacts/doctype/address_template/test_address_template.py b/frappe/contacts/doctype/address_template/test_address_template.py index f40b56e7d9..6b519a3bb7 100644 --- a/frappe/contacts/doctype/address_template/test_address_template.py +++ b/frappe/contacts/doctype/address_template/test_address_template.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe, unittest class TestAddressTemplate(unittest.TestCase): @@ -42,4 +40,4 @@ class TestAddressTemplate(unittest.TestCase): "doctype": "Address Template", "country": 'Brazil', "template": template - }).insert() \ No newline at end of file + }).insert() \ No newline at end of file diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index b3d4c6fc5c..d1dd1f1010 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -1,18 +1,13 @@ # 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.utils import cstr, has_gravatar, cint +from frappe.utils import cstr, has_gravatar from frappe import _ from frappe.model.document import Document from frappe.core.doctype.dynamic_link.dynamic_link import deduplicate_dynamic_links -from six import iteritems -from past.builtins import cmp from frappe.model.naming import append_number_if_name_exists from frappe.contacts.address_and_contact import set_link_title -import functools class Contact(Document): def autoname(self): @@ -120,7 +115,7 @@ class Contact(Document): if len(is_primary) > 1: frappe.throw(_("Only one {0} can be set as primary.").format(frappe.bold(frappe.unscrub(fieldname)))) - primary_number_exists = False + primary_number_exists = False for d in self.phone_nos: if d.get(field_name) == 1: primary_number_exists = True @@ -140,10 +135,13 @@ def get_default_contact(doctype, name): where dl.link_doctype=%s and dl.link_name=%s and - dl.parenttype = "Contact"''', (doctype, name)) + dl.parenttype = "Contact"''', (doctype, name), as_dict=True) if out: - return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(cint(y[1]), cint(x[1]))))[0][0] + for contact in out: + if contact.is_primary_contact: + return contact.parent + return out[0].parent else: return None diff --git a/frappe/contacts/doctype/contact/test_contact.py b/frappe/contacts/doctype/contact/test_contact.py index b131428696..6c6089edeb 100644 --- a/frappe/contacts/doctype/contact/test_contact.py +++ b/frappe/contacts/doctype/contact/test_contact.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/contacts/doctype/contact_email/contact_email.py b/frappe/contacts/doctype/contact_email/contact_email.py index 04e8b22989..5fc2fef316 100644 --- a/frappe/contacts/doctype/contact_email/contact_email.py +++ b/frappe/contacts/doctype/contact_email/contact_email.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/contacts/doctype/contact_phone/contact_phone.py b/frappe/contacts/doctype/contact_phone/contact_phone.py index fe2f86a4bd..63f5f73cf1 100644 --- a/frappe/contacts/doctype/contact_phone/contact_phone.py +++ b/frappe/contacts/doctype/contact_phone/contact_phone.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/contacts/doctype/gender/gender.py b/frappe/contacts/doctype/gender/gender.py index bfca5830c1..319800de7e 100644 --- a/frappe/contacts/doctype/gender/gender.py +++ b/frappe/contacts/doctype/gender/gender.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class Gender(Document): diff --git a/frappe/contacts/doctype/gender/test_gender.py b/frappe/contacts/doctype/gender/test_gender.py index fbe3473bc3..071ed47df0 100644 --- a/frappe/contacts/doctype/gender/test_gender.py +++ b/frappe/contacts/doctype/gender/test_gender.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestGender(unittest.TestCase): diff --git a/frappe/contacts/doctype/salutation/salutation.py b/frappe/contacts/doctype/salutation/salutation.py index d9e4528c7d..d79ad66845 100644 --- a/frappe/contacts/doctype/salutation/salutation.py +++ b/frappe/contacts/doctype/salutation/salutation.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class Salutation(Document): diff --git a/frappe/contacts/doctype/salutation/test_salutation.py b/frappe/contacts/doctype/salutation/test_salutation.py index 63d603e6a4..e2e9075855 100644 --- a/frappe/contacts/doctype/salutation/test_salutation.py +++ b/frappe/contacts/doctype/salutation/test_salutation.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestSalutation(unittest.TestCase): diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py index 1b3982f251..bf48b6b185 100644 --- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.py @@ -1,8 +1,5 @@ # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -from __future__ import unicode_literals -from six import iteritems import frappe from frappe import _ @@ -58,7 +55,7 @@ def get_reference_addresses_and_contact(reference_doctype, reference_name): reference_details = get_reference_details(reference_doctype, "Address", reference_list, reference_details) reference_details = get_reference_details(reference_doctype, "Contact", reference_list, reference_details) - for reference_name, details in iteritems(reference_details): + for reference_name, details in reference_details.items(): addresses = details.get("address", []) contacts = details.get("contact", []) if not any([addresses, contacts]): diff --git a/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py b/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py index 9e98dcf6f6..f539722175 100644 --- a/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py +++ b/frappe/contacts/report/addresses_and_contacts/test_addresses_and_contacts.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe import frappe.defaults import unittest diff --git a/frappe/core/__init__.py b/frappe/core/__init__.py index 998a299158..f064a66c17 100644 --- a/frappe/core/__init__.py +++ b/frappe/core/__init__.py @@ -1,4 +1,2 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals \ No newline at end of file diff --git a/frappe/core/doctype/__init__.py b/frappe/core/doctype/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/__init__.py +++ b/frappe/core/doctype/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/access_log/access_log.py b/frappe/core/doctype/access_log/access_log.py index 43381e7f2e..d2fbee108b 100644 --- a/frappe/core/doctype/access_log/access_log.py +++ b/frappe/core/doctype/access_log/access_log.py @@ -3,8 +3,6 @@ # For license information, please see license.txt # imports - standard imports -from __future__ import unicode_literals - # imports - module imports import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/activity_log/activity_log.py b/frappe/core/doctype/activity_log/activity_log.py index 98dc91806d..efec0dc217 100644 --- a/frappe/core/doctype/activity_log/activity_log.py +++ b/frappe/core/doctype/activity_log/activity_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe import _ from frappe.utils import get_fullname, now from frappe.model.document import Document diff --git a/frappe/core/doctype/activity_log/feed.py b/frappe/core/doctype/activity_log/feed.py index f51692fe9f..caa3cae613 100644 --- a/frappe/core/doctype/activity_log/feed.py +++ b/frappe/core/doctype/activity_log/feed.py @@ -1,13 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals import frappe import frappe.permissions from frappe.utils import get_fullname from frappe import _ from frappe.core.doctype.activity_log.activity_log import add_authentication_log -from six import string_types def update_feed(doc, method=None): if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_import: @@ -23,7 +21,7 @@ def update_feed(doc, method=None): feed = doc.get_feed() if feed: - if isinstance(feed, string_types): + if isinstance(feed, str): feed = {"subject": feed} feed = frappe._dict(feed) diff --git a/frappe/core/doctype/activity_log/test_activity_log.py b/frappe/core/doctype/activity_log/test_activity_log.py index f33c7a1c85..ed7b70cca1 100644 --- a/frappe/core/doctype/activity_log/test_activity_log.py +++ b/frappe/core/doctype/activity_log/test_activity_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest import time diff --git a/frappe/core/doctype/block_module/block_module.py b/frappe/core/doctype/block_module/block_module.py index e7bb3cf045..d9723f9170 100644 --- a/frappe/core/doctype/block_module/block_module.py +++ b/frappe/core/doctype/block_module/block_module.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index ad5d60500b..2706ab1c30 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt - -from __future__ import unicode_literals, absolute_import import frappe from frappe import _ import json @@ -11,7 +9,7 @@ from frappe.core.doctype.user.user import extract_mentions from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification,\ get_title, get_title_html from frappe.utils import get_fullname -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe.database.schema import add_column from frappe.exceptions import ImplicitCommitError diff --git a/frappe/core/doctype/comment/test_comment.py b/frappe/core/doctype/comment/test_comment.py index 3cf8fbaa3f..13db92e7a8 100644 --- a/frappe/core/doctype/comment/test_comment.py +++ b/frappe/core/doctype/comment/test_comment.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe, json import unittest diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 5ebf714645..17b1290776 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -1,29 +1,31 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals, absolute_import from collections import Counter import frappe from frappe import _ from frappe.model.document import Document from frappe.utils import validate_email_address, strip_html, cstr, time_diff_in_seconds -from frappe.core.doctype.communication.email import validate_email, notify, _notify +from frappe.core.doctype.communication.email import validate_email +from frappe.core.doctype.communication.mixins import CommunicationEmailMixin from frappe.core.utils import get_parent_doc from frappe.utils.bot import BotReply -from frappe.utils import parse_addr +from frappe.utils import parse_addr, split_emails from frappe.core.doctype.comment.comment import update_comment_in_doc from email.utils import parseaddr -from six.moves.urllib.parse import unquote +from urllib.parse import unquote from frappe.utils.user import is_system_user from frappe.contacts.doctype.contact.contact import get_contact_name from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule exclude_from_linked_with = True -class Communication(Document): +class Communication(Document, CommunicationEmailMixin): + """Communication represents an external communication like Email. + """ no_feed_on_delete = True + DOCTYPE = 'Communication' - """Communication represents an external communication like Email.""" def onload(self): """create email flag queue""" if self.communication_type == "Communication" and self.communication_medium == "Email" \ @@ -124,6 +126,45 @@ class Communication(Document): if self.communication_type == "Communication": self.notify_change('delete') + @property + def sender_mailid(self): + return parse_addr(self.sender)[1] if self.sender else "" + + @staticmethod + def _get_emails_list(emails=None, exclude_displayname = False): + """Returns list of emails from given email string. + + * Removes duplicate mailids + * Removes display name from email address if exclude_displayname is True + """ + emails = split_emails(emails) if isinstance(emails, str) else (emails or []) + if exclude_displayname: + return [email.lower() for email in set([parse_addr(email)[1] for email in emails]) if email] + return [email.lower() for email in set(emails) if email] + + def to_list(self, exclude_displayname = True): + """Returns to list. + """ + return self._get_emails_list(self.recipients, exclude_displayname=exclude_displayname) + + def cc_list(self, exclude_displayname = True): + """Returns cc list. + """ + return self._get_emails_list(self.cc, exclude_displayname=exclude_displayname) + + def bcc_list(self, exclude_displayname = True): + """Returns bcc list. + """ + return self._get_emails_list(self.bcc, exclude_displayname=exclude_displayname) + + def get_attachments(self): + attachments = frappe.get_all( + "File", + fields=["name", "file_name", "file_url", "is_private"], + filters = {"attached_to_name": self.name, "attached_to_doctype": self.DOCTYPE} + ) + return attachments + def notify_change(self, action): frappe.publish_realtime('update_docinfo_for_{}_{}'.format(self.reference_doctype, self.reference_name), { 'doc': self.as_dict(), @@ -149,6 +190,23 @@ class Communication(Document): self.email_status = "Spam" + @classmethod + def find(cls, name, ignore_error=False): + try: + return frappe.get_doc(cls.DOCTYPE, name) + except frappe.DoesNotExistError: + if ignore_error: + return + raise + + @classmethod + def find_one_by_filters(cls, *, order_by=None, **kwargs): + name = frappe.db.get_value(cls.DOCTYPE, kwargs, order_by=order_by) + return cls.find(name) if name else None + + def update_db(self, **kwargs): + frappe.db.set_value(self.DOCTYPE, self.name, kwargs) + def set_sender_full_name(self): if not self.sender_full_name and self.sender: if self.sender == "Administrator": @@ -180,36 +238,6 @@ class Communication(Document): if not self.sender_full_name: self.sender_full_name = sender_email - def send(self, print_html=None, print_format=None, attachments=None, - send_me_a_copy=False, recipients=None): - """Send communication via Email. - - :param print_html: Send given value as HTML attachment. - :param print_format: Attach print format of parent document.""" - - self.send_me_a_copy = send_me_a_copy - self.notify(print_html, print_format, attachments, recipients) - - def notify(self, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, bcc=None,fetched_from_email_account=False): - """Calls a delayed task 'sendmail' that enqueus email in Email Queue queue - - :param print_html: Send given value as HTML attachment - :param print_format: Attach print format of parent document - :param attachments: A list of filenames that should be attached when sending this email - :param recipients: Email recipients - :param cc: Send email as CC to - :param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient - - """ - notify(self, print_html, print_format, attachments, recipients, cc, bcc, - fetched_from_email_account) - - def _notify(self, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, bcc=None): - - _notify(self, print_html, print_format, attachments, recipients, cc, bcc) - def bot_reply(self): if self.comment_type == 'Bot' and self.communication_type == 'Chat': reply = BotReply().get_reply(self.content) @@ -485,4 +513,5 @@ def set_avg_response_time(parent, communication): response_times.append(response_time) if response_times: avg_response_time = sum(response_times) / len(response_times) - parent.db_set("avg_response_time", avg_response_time) \ No newline at end of file + parent.db_set("avg_response_time", avg_response_time) + diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py index d3017055cf..7ffbe6781d 100755 --- a/frappe/core/doctype/communication/email.py +++ b/frappe/core/doctype/communication/email.py @@ -1,9 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals, absolute_import -from six.moves import range -from six import string_types import frappe import json from email.utils import formataddr @@ -16,6 +13,11 @@ import time from frappe import _ from frappe.utils.background_jobs import enqueue +OUTGOING_EMAIL_ACCOUNT_MISSING = _(""" + Unable to send mail because of a missing email account. + Please setup default Email Account from Setup > Email > Email Account +""") + @frappe.whitelist() def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent", sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False, @@ -39,7 +41,6 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = :param send_me_a_copy: Send a copy to the sender (default **False**). :param email_template: Template which is used to compose mail . """ - is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report") send_me_a_copy = cint(send_me_a_copy) @@ -77,22 +78,24 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received = comm.save(ignore_permissions=True) - if isinstance(attachments, string_types): + if isinstance(attachments, str): attachments = json.loads(attachments) # if not committed, delayed task doesn't find the communication if attachments: add_attachments(comm.name, attachments) - frappe.db.commit() - if cint(send_email): - frappe.flags.print_letterhead = cint(print_letterhead) - comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy) + if not comm.get_outgoing_email_account(): + frappe.throw(msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError) + comm.send_email(print_html=print_html, print_format=print_format, + send_me_a_copy=send_me_a_copy, print_letterhead=print_letterhead) + + emails_not_sent_to = comm.exclude_emails_list(include_sender=send_me_a_copy) return { "name": comm.name, - "emails_not_sent_to": ", ".join(comm.emails_not_sent_to) if hasattr(comm, "emails_not_sent_to") else None + "emails_not_sent_to": ", ".join(emails_not_sent_to or []) } def validate_email(doc): @@ -113,164 +116,6 @@ def validate_email(doc): # validate sender -def notify(doc, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, bcc=None, fetched_from_email_account=False): - """Calls a delayed task 'sendmail' that enqueus email in Email Queue queue - - :param print_html: Send given value as HTML attachment - :param print_format: Attach print format of parent document - :param attachments: A list of filenames that should be attached when sending this email - :param recipients: Email recipients - :param cc: Send email as CC to - :param bcc: Send email as BCC to - :param fetched_from_email_account: True when pulling email, the notification shouldn't go to the main recipient - - """ - recipients, cc, bcc = get_recipients_cc_and_bcc(doc, recipients, cc, bcc, - fetched_from_email_account=fetched_from_email_account) - - if not recipients and not cc: - return - - doc.emails_not_sent_to = set(doc.all_email_addresses) - set(doc.sent_email_addresses) - - if frappe.flags.in_test: - # for test cases, run synchronously - doc._notify(print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc, bcc=None) - else: - enqueue(sendmail, queue="default", timeout=300, event="sendmail", - communication_name=doc.name, - print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc, bcc=bcc, lang=frappe.local.lang, - session=frappe.local.session, print_letterhead=frappe.flags.print_letterhead) - -def _notify(doc, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, bcc=None): - - prepare_to_notify(doc, print_html, print_format, attachments) - - if doc.outgoing_email_account.send_unsubscribe_message: - unsubscribe_message = _("Leave this conversation") - else: - unsubscribe_message = "" - - frappe.sendmail( - recipients=(recipients or []), - cc=(cc or []), - bcc=(bcc or []), - expose_recipients="header", - sender=doc.sender, - reply_to=doc.incoming_email_account, - subject=doc.subject, - content=doc.content, - reference_doctype=doc.reference_doctype, - reference_name=doc.reference_name, - attachments=doc.attachments, - message_id=doc.message_id, - unsubscribe_message=unsubscribe_message, - delayed=True, - communication=doc.name, - read_receipt=doc.read_receipt, - is_notification=True if doc.sent_or_received =="Received" else False, - print_letterhead=frappe.flags.print_letterhead - ) - -def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=False): - doc.all_email_addresses = [] - doc.sent_email_addresses = [] - doc.previous_email_sender = None - - if not recipients: - recipients = get_recipients(doc, fetched_from_email_account=fetched_from_email_account) - - if not cc: - cc = get_cc(doc, recipients, fetched_from_email_account=fetched_from_email_account) - - if not bcc: - bcc = get_bcc(doc, recipients, fetched_from_email_account=fetched_from_email_account) - - if fetched_from_email_account: - # email was already sent to the original recipient by the sender's email service - original_recipients, recipients = recipients, [] - - # send email to the sender of the previous email in the thread which this email is a reply to - #provides erratic results and can send external - #if doc.previous_email_sender: - # recipients.append(doc.previous_email_sender) - - # cc that was received in the email - original_cc = split_emails(doc.cc) - - # don't cc to people who already received the mail from sender's email service - cc = list(set(cc) - set(original_cc) - set(original_recipients)) - remove_administrator_from_email_list(cc) - - original_bcc = split_emails(doc.bcc) - bcc = list(set(bcc) - set(original_bcc) - set(original_recipients)) - remove_administrator_from_email_list(bcc) - - remove_administrator_from_email_list(recipients) - - return recipients, cc, bcc - -def remove_administrator_from_email_list(email_list): - administrator_email = list(filter(lambda emails: "Administrator" in emails, email_list)) - if administrator_email: - email_list.remove(administrator_email[0]) - -def prepare_to_notify(doc, print_html=None, print_format=None, attachments=None): - """Prepare to make multipart MIME Email - - :param print_html: Send given value as HTML attachment. - :param print_format: Attach print format of parent document.""" - - view_link = frappe.utils.cint(frappe.db.get_value("System Settings", "System Settings", "attach_view_link")) - - if print_format and view_link: - doc.content += get_attach_link(doc, print_format) - - set_incoming_outgoing_accounts(doc) - - if not doc.sender: - doc.sender = doc.outgoing_email_account.email_id - - if not doc.sender_full_name: - doc.sender_full_name = doc.outgoing_email_account.name or _("Notification") - - if doc.sender: - # combine for sending to get the format 'Jane ' - doc.sender = get_formatted_email(doc.sender_full_name, mail=doc.sender) - - doc.attachments = [] - - if print_html or print_format: - doc.attachments.append({"print_format_attachment":1, "doctype":doc.reference_doctype, - "name":doc.reference_name, "print_format":print_format, "html":print_html}) - - if attachments: - if isinstance(attachments, string_types): - attachments = json.loads(attachments) - - for a in attachments: - if isinstance(a, string_types): - # is it a filename? - try: - # check for both filename and file id - file_id = frappe.db.get_list('File', or_filters={'file_name': a, 'name': a}, limit=1) - if not file_id: - frappe.throw(_("Unable to find attachment {0}").format(a)) - file_id = file_id[0]['name'] - _file = frappe.get_doc("File", file_id) - _file.get_content() - # these attachments will be attached on-demand - # and won't be stored in the message - doc.attachments.append({"fid": file_id}) - except IOError: - frappe.throw(_("Unable to find attachment {0}").format(a)) - else: - doc.attachments.append(a) - def set_incoming_outgoing_accounts(doc): from frappe.email.doctype.email_account.email_account import EmailAccount incoming_email_account = EmailAccount.find_incoming( @@ -283,82 +128,13 @@ def set_incoming_outgoing_accounts(doc): if doc.sent_or_received == "Sent": doc.db_set("email_account", doc.outgoing_email_account.name) -def get_recipients(doc, fetched_from_email_account=False): - """Build a list of email addresses for To""" - # [EDGE CASE] doc.recipients can be None when an email is sent as BCC - recipients = split_emails(doc.recipients) - - #if fetched_from_email_account and doc.in_reply_to: - # add sender of previous reply - #doc.previous_email_sender = frappe.db.get_value("Communication", doc.in_reply_to, "sender") - #recipients.append(doc.previous_email_sender) - - if recipients: - recipients = filter_email_list(doc, recipients, []) - - return recipients - -def get_cc(doc, recipients=None, fetched_from_email_account=False): - """Build a list of email addresses for CC""" - # get a copy of CC list - cc = split_emails(doc.cc) - - if doc.reference_doctype and doc.reference_name: - if fetched_from_email_account: - # if it is a fetched email, add follows to CC - cc.append(get_owner_email(doc)) - cc += get_assignees(doc) - - if getattr(doc, "send_me_a_copy", False) and doc.sender not in cc: - cc.append(doc.sender) - - if cc: - # exclude unfollows, recipients and unsubscribes - exclude = [] #added to remove account check - exclude += [d[0] for d in frappe.db.get_all("User", ["email"], {"thread_notify": 0}, as_list=True)] - exclude += [(parse_addr(email)[1] or "").lower() for email in recipients] - - if fetched_from_email_account: - # exclude sender when pulling email - exclude += [parse_addr(doc.sender)[1]] - - if doc.reference_doctype and doc.reference_name: - exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"], - {"reference_doctype": doc.reference_doctype, "reference_name": doc.reference_name}, as_list=True)] - - cc = filter_email_list(doc, cc, exclude, is_cc=True) - - return cc - -def get_bcc(doc, recipients=None, fetched_from_email_account=False): - """Build a list of email addresses for BCC""" - bcc = split_emails(doc.bcc) - - if bcc: - exclude = [] - exclude += [d[0] for d in frappe.db.get_all("User", ["email"], {"thread_notify": 0}, as_list=True)] - exclude += [(parse_addr(email)[1] or "").lower() for email in recipients] - - if fetched_from_email_account: - # exclude sender when pulling email - exclude += [parse_addr(doc.sender)[1]] - - if doc.reference_doctype and doc.reference_name: - exclude += [d[0] for d in frappe.db.get_all("Email Unsubscribe", ["email"], - {"reference_doctype": doc.reference_doctype, "reference_name": doc.reference_name}, as_list=True)] - - bcc = filter_email_list(doc, bcc, exclude, is_bcc=True) - - return bcc - def add_attachments(name, attachments): '''Add attachments to the given Communication''' # loop through attachments for a in attachments: - if isinstance(a, string_types): + if isinstance(a, str): attach = frappe.db.get_value("File", {"name":a}, ["file_name", "file_url", "is_private"], as_dict=1) - # save attachments to new doc _file = frappe.get_doc({ "doctype": "File", @@ -370,103 +146,6 @@ def add_attachments(name, attachments): }) _file.save(ignore_permissions=True) -def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False): - # temp variables - filtered = [] - email_address_list = [] - - for email in list(set(email_list)): - email_address = (parse_addr(email)[1] or "").lower() - if not email_address: - continue - - # this will be used to eventually find email addresses that aren't sent to - doc.all_email_addresses.append(email_address) - - if (email in exclude) or (email_address in exclude): - continue - - if is_cc: - is_user_enabled = frappe.db.get_value("User", email_address, "enabled") - if is_user_enabled==0: - # don't send to disabled users - continue - - if is_bcc: - is_user_enabled = frappe.db.get_value("User", email_address, "enabled") - if is_user_enabled==0: - continue - - # make sure of case-insensitive uniqueness of email address - if email_address not in email_address_list: - # append the full email i.e. "Human " - filtered.append(email) - email_address_list.append(email_address) - - doc.sent_email_addresses.extend(email_address_list) - - return filtered - -def get_owner_email(doc): - owner = get_parent_doc(doc).owner - return get_formatted_email(owner) or owner - -def get_assignees(doc): - return [( get_formatted_email(d.owner) or d.owner ) for d in - frappe.db.get_all("ToDo", filters={ - "reference_type": doc.reference_doctype, - "reference_name": doc.reference_name, - "status": "Open" - }, fields=["owner"]) - ] - -def get_attach_link(doc, print_format): - """Returns public link for the attachment via `templates/emails/print_link.html`.""" - return frappe.get_template("templates/emails/print_link.html").render({ - "url": get_url(), - "doctype": doc.reference_doctype, - "name": doc.reference_name, - "print_format": print_format, - "key": get_parent_doc(doc).get_signature() - }) - -def sendmail(communication_name, print_html=None, print_format=None, attachments=None, - recipients=None, cc=None, bcc=None, lang=None, session=None, print_letterhead=None): - try: - - if lang: - frappe.local.lang = lang - - if session: - # hack to enable access to private files in PDF - session['data'] = frappe._dict(session['data']) - frappe.local.session.update(session) - - if print_letterhead: - frappe.flags.print_letterhead = print_letterhead - - # upto 3 retries - for i in range(3): - try: - communication = frappe.get_doc("Communication", communication_name) - communication._notify(print_html=print_html, print_format=print_format, attachments=attachments, - recipients=recipients, cc=cc, bcc=bcc) - - except frappe.db.InternalError as e: - # deadlock, try again - if frappe.db.is_deadlocked(e): - frappe.db.rollback() - time.sleep(1) - continue - else: - raise - else: - break - - except: - traceback = frappe.log_error("frappe.core.doctype.communication.email.sendmail") - raise - @frappe.whitelist(allow_guest=True) def mark_email_as_seen(name=None): try: diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py new file mode 100644 index 0000000000..82a47d24d9 --- /dev/null +++ b/frappe/core/doctype/communication/mixins.py @@ -0,0 +1,297 @@ +import frappe +from frappe import _ +from frappe.core.utils import get_parent_doc +from frappe.utils import parse_addr, get_formatted_email, get_url +from frappe.email.doctype.email_account.email_account import EmailAccount + +class CommunicationEmailMixin: + """Mixin class to handle communication mails. + """ + def is_email_communication(self): + return self.communication_type=="Communication" and self.communication_medium == "Email" + + def get_owner(self): + """Get owner of the communication docs parent. + """ + parent_doc = get_parent_doc(self) + return parent_doc.owner if parent_doc else None + + def get_all_email_addresses(self, exclude_displayname=False): + """Get all Email addresses mentioned in the doc along with display name. + """ + return self.to_list(exclude_displayname=exclude_displayname) + \ + self.cc_list(exclude_displayname=exclude_displayname) + \ + self.bcc_list(exclude_displayname=exclude_displayname) + + def get_email_with_displayname(self, email_address): + """Returns email address after adding displayname. + """ + display_name, email = parse_addr(email_address) + if display_name and display_name != email: + return email_address + + # emailid to emailid with display name map. + email_map = {parse_addr(email)[1]: email for email in self.get_all_email_addresses()} + return email_map.get(email, email) + + def mail_recipients(self, is_inbound_mail_communcation=False): + """Build to(recipient) list to send an email. + """ + # Incase of inbound mail, recipients already received the mail, no need to send again. + if is_inbound_mail_communcation: + return [] + + if hasattr(self, '_final_recipients'): + return self._final_recipients + + to = self.to_list() + self._final_recipients = list(filter(lambda id: id != 'Administrator', to)) + return self._final_recipients + + def get_mail_recipients_with_displayname(self, is_inbound_mail_communcation=False): + """Build to(recipient) list to send an email including displayname in email. + """ + to_list = self.mail_recipients(is_inbound_mail_communcation=is_inbound_mail_communcation) + return [self.get_email_with_displayname(email) for email in to_list] + + def mail_cc(self, is_inbound_mail_communcation=False, include_sender = False): + """Build cc list to send an email. + + * if email copy is requested by sender, then add sender to CC. + * If this doc is created through inbound mail, then add doc owner to cc list + * remove all the thread_notify disabled users. + * Make sure that all users enabled in the system + * Remove admin from email list + + * FixMe: Removed adding TODO owners to cc list. Check if that is needed. + """ + if hasattr(self, '_final_cc'): + return self._final_cc + + cc = self.cc_list() + + # Need to inform parent document owner incase communication is created through inbound mail + if include_sender: + cc.append(self.sender_mailid) + if is_inbound_mail_communcation: + cc.append(self.get_owner()) + cc = set(cc) - {self.sender_mailid} + + cc = set(cc) - set(self.filter_thread_notification_disbled_users(cc)) + cc = cc - set(self.mail_recipients(is_inbound_mail_communcation=is_inbound_mail_communcation)) + cc = cc - set(self.filter_disabled_users(cc)) + + # # Incase of inbound mail, to and cc already received the mail, no need to send again. + if is_inbound_mail_communcation: + cc = cc - set(self.cc_list() + self.to_list()) + + self._final_cc = list(filter(lambda id: id != 'Administrator', cc)) + return self._final_cc + + def get_mail_cc_with_displayname(self, is_inbound_mail_communcation=False, include_sender = False): + cc_list = self.mail_cc(is_inbound_mail_communcation=False, include_sender = False) + return [self.get_email_with_displayname(email) for email in cc_list] + + def mail_bcc(self, is_inbound_mail_communcation=False): + """ + * Thread_notify check + * Email unsubscribe list + * User must be enabled in the system + * remove_administrator_from_email_list + """ + if hasattr(self, '_final_bcc'): + return self._final_bcc + + bcc = set(self.bcc_list()) + if is_inbound_mail_communcation: + bcc = bcc - {self.sender_mailid} + bcc = bcc - set(self.filter_thread_notification_disbled_users(bcc)) + bcc = bcc - set(self.mail_recipients(is_inbound_mail_communcation=is_inbound_mail_communcation)) + bcc = bcc - set(self.filter_disabled_users(bcc)) + + # Incase of inbound mail, to and cc & bcc already received the mail, no need to send again. + if is_inbound_mail_communcation: + bcc = bcc - set(self.bcc_list() + self.to_list()) + + self._final_bcc = list(filter(lambda id: id != 'Administrator', bcc)) + return self._final_bcc + + def get_mail_bcc_with_displayname(self, is_inbound_mail_communcation=False): + bcc_list = self.mail_bcc(is_inbound_mail_communcation=is_inbound_mail_communcation) + return [self.get_email_with_displayname(email) for email in bcc_list] + + def mail_sender(self): + email_account = self.get_outgoing_email_account() + if not self.sender_mailid and email_account: + return email_account.email_id + return self.sender_mailid + + def mail_sender_fullname(self): + email_account = self.get_outgoing_email_account() + if not self.sender_full_name: + return (email_account and email_account.name) or _("Notification") + return self.sender_full_name + + def get_mail_sender_with_displayname(self): + return get_formatted_email(self.mail_sender_fullname(), mail=self.mail_sender()) + + def get_content(self, print_format=None): + if print_format: + return self.content + self.get_attach_link(print_format) + return self.content + + def get_attach_link(self, print_format): + """Returns public link for the attachment via `templates/emails/print_link.html`.""" + return frappe.get_template("templates/emails/print_link.html").render({ + "url": get_url(), + "doctype": self.reference_doctype, + "name": self.reference_name, + "print_format": print_format, + "key": get_parent_doc(self).get_signature() + }) + + def get_outgoing_email_account(self): + if not hasattr(self, '_outgoing_email_account'): + if self.email_account: + self._outgoing_email_account = EmailAccount.find(self.email_account) + else: + self._outgoing_email_account = EmailAccount.find_outgoing( + match_by_email=self.sender_mailid, + match_by_doctype=self.reference_doctype + ) + + if self.sent_or_received == "Sent" and self._outgoing_email_account: + self.db_set("email_account", self._outgoing_email_account.name) + + return self._outgoing_email_account + + def get_incoming_email_account(self): + if not hasattr(self, '_incoming_email_account'): + self._incoming_email_account = EmailAccount.find_incoming( + match_by_email=self.sender_mailid, + match_by_doctype=self.reference_doctype + ) + return self._incoming_email_account + + def mail_attachments(self, print_format=None, print_html=None): + final_attachments = [] + + if print_format and print_html: + d = {'print_format': print_format, 'print_html': print_html, 'print_format_attachment': 1, + 'doctype': self.reference_doctype, 'name': self.reference_name} + final_attachments.append(d) + + for a in self.get_attachments() or []: + final_attachments.append({"fid": a['name']}) + + return final_attachments + + def get_unsubscribe_message(self): + email_account = self.get_outgoing_email_account() + if email_account and email_account.send_unsubscribe_message: + return _("Leave this conversation") + return '' + + def exclude_emails_list(self, is_inbound_mail_communcation=False, include_sender=False): + """List of mail id's excluded while sending mail. + """ + all_ids = self.get_all_email_addresses(exclude_displayname=True) + final_ids = self.mail_recipients(is_inbound_mail_communcation = is_inbound_mail_communcation) + \ + self.mail_bcc(is_inbound_mail_communcation = is_inbound_mail_communcation) + \ + self.mail_cc(is_inbound_mail_communcation = is_inbound_mail_communcation, include_sender=include_sender) + return set(all_ids) - set(final_ids) + + @staticmethod + def filter_thread_notification_disbled_users(emails): + """Filter users based on notifications for email threads setting is disabled. + """ + if not emails: + return [] + + disabled_users = frappe.db.sql_list(""" + SELECT + email + FROM + `tabUser` + where + email in %(emails)s + and + thread_notify=0 + """, {'emails': tuple(emails)}) + return disabled_users + + @staticmethod + def filter_disabled_users(emails): + """ + """ + if not emails: + return [] + + disabled_users = frappe.db.sql_list(""" + SELECT + email + FROM + `tabUser` + where + email in %(emails)s + and + enabled=0 + """, {'emails': tuple(emails)}) + return disabled_users + + def sendmail_input_dict(self, print_html=None, print_format=None, + send_me_a_copy=None, print_letterhead=None, is_inbound_mail_communcation=None): + + outgoing_email_account = self.get_outgoing_email_account() + if not outgoing_email_account: + return {} + + recipients = self.get_mail_recipients_with_displayname( + is_inbound_mail_communcation=is_inbound_mail_communcation + ) + cc = self.get_mail_cc_with_displayname( + is_inbound_mail_communcation=is_inbound_mail_communcation, + include_sender = send_me_a_copy + ) + bcc = self.get_mail_bcc_with_displayname( + is_inbound_mail_communcation=is_inbound_mail_communcation + ) + + if not (recipients or cc): + return {} + + final_attachments = self.mail_attachments(print_format=print_format, print_html=print_html) + incoming_email_account = self.get_incoming_email_account() + return { + "recipients": recipients, + "cc": cc, + "bcc": bcc, + "expose_recipients": "header", + "sender": self.get_mail_sender_with_displayname(), + "reply_to": incoming_email_account and incoming_email_account.email_id, + "subject": self.subject, + "content": self.get_content(print_format=print_format), + "reference_doctype": self.reference_doctype, + "reference_name": self.reference_name, + "attachments": final_attachments, + "message_id": self.message_id, + "unsubscribe_message": self.get_unsubscribe_message(), + "delayed": True, + "communication": self.name, + "read_receipt": self.read_receipt, + "is_notification": (self.sent_or_received =="Received" and True) or False, + "print_letterhead": print_letterhead + } + + def send_email(self, print_html=None, print_format=None, + send_me_a_copy=None, print_letterhead=None, is_inbound_mail_communcation=None): + input_dict = self.sendmail_input_dict( + print_html=print_html, + print_format=print_format, + send_me_a_copy=send_me_a_copy, + print_letterhead=print_letterhead, + is_inbound_mail_communcation=is_inbound_mail_communcation + ) + + if input_dict: + frappe.sendmail(**input_dict) diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py index 6df90baaae..d50a4db88a 100644 --- a/frappe/core/doctype/communication/test_communication.py +++ b/frappe/core/doctype/communication/test_communication.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals +import unittest +from urllib.parse import quote import frappe -import unittest -from six.moves.urllib.parse import quote -test_records = frappe.get_test_records('Communication') +from frappe.email.doctype.email_queue.email_queue import EmailQueue +test_records = frappe.get_test_records('Communication') class TestCommunication(unittest.TestCase): @@ -201,6 +201,70 @@ class TestCommunication(unittest.TestCase): self.assertIn(("Note", note.name), doc_links) +class TestCommunicationEmailMixin(unittest.TestCase): + def new_communication(self, recipients=None, cc=None, bcc=None): + recipients = ', '.join(recipients or []) + cc = ', '.join(cc or []) + bcc = ', '.join(bcc or []) + + comm = frappe.get_doc({ + "doctype": "Communication", + "communication_type": "Communication", + "communication_medium": "Email", + "content": "Test content", + "recipients": recipients, + "cc": cc, + "bcc": bcc + }).insert(ignore_permissions=True) + return comm + + def new_user(self, email, **user_data): + user_data.setdefault('first_name', 'first_name') + user = frappe.new_doc('User') + user.email = email + user.update(user_data) + user.insert(ignore_permissions=True, ignore_if_duplicate=True) + return user + + def test_recipients(self): + to_list = ['to@test.com', 'receiver ', 'to@test.com'] + comm = self.new_communication(recipients = to_list) + res = comm.get_mail_recipients_with_displayname() + self.assertCountEqual(res, ['to@test.com', 'receiver ']) + comm.delete() + + def test_cc(self): + to_list = ['to@test.com'] + cc_list = ['cc+1@test.com', 'cc ', 'to@test.com'] + user = self.new_user(email='cc+1@test.com', thread_notify=0) + comm = self.new_communication(recipients=to_list, cc=cc_list) + res = comm.get_mail_cc_with_displayname() + self.assertCountEqual(res, ['cc ']) + user.delete() + comm.delete() + + def test_bcc(self): + bcc_list = ['bcc+1@test.com', 'cc ', ] + user = self.new_user(email='bcc+2@test.com', enabled=0) + comm = self.new_communication(bcc=bcc_list) + res = comm.get_mail_bcc_with_displayname() + self.assertCountEqual(res, ['bcc+1@test.com']) + user.delete() + comm.delete() + + def test_sendmail(self): + to_list = ['to '] + cc_list = ['cc ', 'cc '] + + comm = self.new_communication(recipients=to_list, cc=cc_list) + comm.send_email() + doc = EmailQueue.find_one_by_filters(communication=comm.name) + mail_receivers = [each.recipient for each in doc.recipients] + self.assertIsNotNone(doc) + self.assertCountEqual(to_list+cc_list, mail_receivers) + doc.delete() + comm.delete() + def create_email_account(): frappe.delete_doc_if_exists("Email Account", "_Test Comm Account 1") @@ -231,4 +295,4 @@ def create_email_account(): "enable_automatic_linking": 1 }).insert(ignore_permissions=True) - return email_account \ No newline at end of file + return email_account diff --git a/frappe/core/doctype/communication_link/communication_link.py b/frappe/core/doctype/communication_link/communication_link.py index d1612ef57e..d3307d1d32 100644 --- a/frappe/core/doctype/communication_link/communication_link.py +++ b/frappe/core/doctype/communication_link/communication_link.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/custom_docperm/custom_docperm.py b/frappe/core/doctype/custom_docperm/custom_docperm.py index cce9788b73..225f5db79b 100644 --- a/frappe/core/doctype/custom_docperm/custom_docperm.py +++ b/frappe/core/doctype/custom_docperm/custom_docperm.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/custom_docperm/test_custom_docperm.py b/frappe/core/doctype/custom_docperm/test_custom_docperm.py index bd6e17ccc9..6e0c82d1db 100644 --- a/frappe/core/doctype/custom_docperm/test_custom_docperm.py +++ b/frappe/core/doctype/custom_docperm/test_custom_docperm.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/custom_role/custom_role.py b/frappe/core/doctype/custom_role/custom_role.py index 25257e1a23..89e478dd38 100644 --- a/frappe/core/doctype/custom_role/custom_role.py +++ b/frappe/core/doctype/custom_role/custom_role.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/custom_role/test_custom_role.py b/frappe/core/doctype/custom_role/test_custom_role.py index 670b494b10..0ad77524fa 100644 --- a/frappe/core/doctype/custom_role/test_custom_role.py +++ b/frappe/core/doctype/custom_role/test_custom_role.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/data_export/data_export.py b/frappe/core/doctype/data_export/data_export.py index fb4fae26d5..c376b25230 100644 --- a/frappe/core/doctype/data_export/data_export.py +++ b/frappe/core/doctype/data_export/data_export.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class DataExport(Document): diff --git a/frappe/core/doctype/data_export/exporter.py b/frappe/core/doctype/data_export/exporter.py index 5d600cc0db..389948449e 100644 --- a/frappe/core/doctype/data_export/exporter.py +++ b/frappe/core/doctype/data_export/exporter.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe from frappe import _ import frappe.permissions @@ -10,7 +8,6 @@ import re, csv, os from frappe.utils.csvutils import UnicodeWriter from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint, format_duration from frappe.core.doctype.data_import_legacy.importer import get_data_keys -from six import string_types from frappe.core.doctype.access_log.access_log import make_access_log reflags = { @@ -57,7 +54,7 @@ class DataExporter: self.docs_to_export = {} if self.doctype: - if isinstance(self.doctype, string_types): + if isinstance(self.doctype, str): self.doctype = [self.doctype] if len(self.doctype) > 1: diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js index 079bdaa09c..216db53c72 100644 --- a/frappe/core/doctype/data_import/data_import.js +++ b/frappe/core/doctype/data_import/data_import.js @@ -91,7 +91,7 @@ frappe.ui.form.on('Data Import', { if (frm.doc.status.includes('Success')) { frm.add_custom_button( - __('Go to {0} List', [frm.doc.reference_doctype]), + __('Go to {0} List', [__(frm.doc.reference_doctype)]), () => frappe.set_route('List', frm.doc.reference_doctype) ); } diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index d3f981add4..bb922f1f5d 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import os import io import frappe @@ -450,7 +449,7 @@ class ImportFile: for row in data_without_first_row: row_values = row.get_values(parent_column_indexes) # if the row is blank, it's a child row doc - if all([v in INVALID_VALUES for v in row_values]): + if all(v in INVALID_VALUES for v in row_values): rows.append(row) continue # if we encounter a row which has values in parent columns, @@ -607,7 +606,7 @@ class Row: if df.fieldtype == "Select": 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]) + 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,} @@ -903,7 +902,7 @@ class Column: 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])) + values = list({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)}) ] @@ -936,11 +935,11 @@ class Column: 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)) + values = {cstr(v) for v in self.column_values[1:] if v} + invalid = 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]) + 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, diff --git a/frappe/core/doctype/data_import/test_data_import.py b/frappe/core/doctype/data_import/test_data_import.py index 15fd57744a..c9366a97ba 100644 --- a/frappe/core/doctype/data_import/test_data_import.py +++ b/frappe/core/doctype/data_import/test_data_import.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/data_import/test_exporter.py b/frappe/core/doctype/data_import/test_exporter.py index 8415af2e63..dfe9926906 100644 --- a/frappe/core/doctype/data_import/test_exporter.py +++ b/frappe/core/doctype/data_import/test_exporter.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe from frappe.core.doctype.data_import.exporter import Exporter diff --git a/frappe/core/doctype/data_import/test_importer.py b/frappe/core/doctype/data_import/test_importer.py index f76d4504a4..7a4d185d8f 100644 --- a/frappe/core/doctype/data_import/test_importer.py +++ b/frappe/core/doctype/data_import/test_importer.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe from frappe.core.doctype.data_import.importer import Importer @@ -64,9 +62,9 @@ class TestImporter(unittest.TestCase): data_import.reload() import_log = frappe.parse_json(data_import.import_log) self.assertEqual(import_log[0]['row_indexes'], [2,3]) - expected_error = "Error: Child 1 of DocType for Import Row #1: Value missing for: Child Title" + expected_error = "Error: Child 1 of DocType for Import Row #1: Value missing for: Child Title" self.assertEqual(frappe.parse_json(import_log[0]['messages'][0])['message'], expected_error) - expected_error = "Error: Child 1 of DocType for Import Row #2: Value missing for: Child Title" + expected_error = "Error: Child 1 of DocType for Import Row #2: Value missing for: Child Title" self.assertEqual(frappe.parse_json(import_log[0]['messages'][1])['message'], expected_error) self.assertEqual(import_log[1]['row_indexes'], [4]) diff --git a/frappe/core/doctype/data_import_legacy/importer.py b/frappe/core/doctype/data_import_legacy/importer.py index 35569c7186..ceefff4410 100644 --- a/frappe/core/doctype/data_import_legacy/importer.py +++ b/frappe/core/doctype/data_import_legacy/importer.py @@ -3,9 +3,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals, print_function - -from six.moves import range import requests import frappe, json import frappe.permissions @@ -16,7 +13,6 @@ from frappe.utils.csvutils import getlink from frappe.utils.dateutils import parse_date from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url, duration_to_seconds -from six import string_types @frappe.whitelist() @@ -42,7 +38,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, frappe.cache().hdel("lang", user) frappe.set_user_lang(user) - if data_import_doc and isinstance(data_import_doc, string_types): + if data_import_doc and isinstance(data_import_doc, str): data_import_doc = frappe.get_doc("Data Import Legacy", data_import_doc) if data_import_doc and from_data_import == "Yes": no_email = data_import_doc.no_email @@ -152,7 +148,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, elif fieldtype in ("Float", "Currency", "Percent"): d[fieldname] = flt(d[fieldname]) elif fieldtype == "Date": - if d[fieldname] and isinstance(d[fieldname], string_types): + if d[fieldname] and isinstance(d[fieldname], str): d[fieldname] = getdate(parse_date(d[fieldname])) elif fieldtype == "Datetime": if d[fieldname]: @@ -181,7 +177,7 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, if d.get("name") and d["name"].startswith('"'): d["name"] = d["name"][1:-1] - if sum([0 if not val else 1 for val in d.values()]): + if sum(0 if not val else 1 for val in d.values()): d['doctype'] = dt if dt == doctype: doc.update(d) @@ -537,6 +533,6 @@ def get_parent_field(doctype, parenttype): def delete_child_rows(rows, doctype): """delete child rows for all parents""" - for p in list(set([r[1] for r in rows])): + for p in list(set(r[1] for r in rows)): if p: frappe.db.sql("""delete from `tab{0}` where parent=%s""".format(doctype), p) diff --git a/frappe/core/doctype/data_import_legacy/test_data_import_legacy.py b/frappe/core/doctype/data_import_legacy/test_data_import_legacy.py index e5b244e6a0..6f9964e8f5 100644 --- a/frappe/core/doctype/data_import_legacy/test_data_import_legacy.py +++ b/frappe/core/doctype/data_import_legacy/test_data_import_legacy.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/defaultvalue/__init__.py b/frappe/core/doctype/defaultvalue/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/defaultvalue/__init__.py +++ b/frappe/core/doctype/defaultvalue/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/defaultvalue/defaultvalue.py b/frappe/core/doctype/defaultvalue/defaultvalue.py index d9cc145053..0ae088ee96 100644 --- a/frappe/core/doctype/defaultvalue/defaultvalue.py +++ b/frappe/core/doctype/defaultvalue/defaultvalue.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 116fc5caf5..f4109c8197 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import json from frappe.desk.doctype.bulk_update.bulk_update import show_progress diff --git a/frappe/core/doctype/deleted_document/test_deleted_document.py b/frappe/core/doctype/deleted_document/test_deleted_document.py index c45a2bd180..d9dc2bb2d1 100644 --- a/frappe/core/doctype/deleted_document/test_deleted_document.py +++ b/frappe/core/doctype/deleted_document/test_deleted_document.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/docfield/__init__.py b/frappe/core/doctype/docfield/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/docfield/__init__.py +++ b/frappe/core/doctype/docfield/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/docfield/docfield.py b/frappe/core/doctype/docfield/docfield.py index b6e2d9b67d..175cba3c7c 100644 --- a/frappe/core/doctype/docfield/docfield.py +++ b/frappe/core/doctype/docfield/docfield.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/docperm/__init__.py b/frappe/core/doctype/docperm/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/docperm/__init__.py +++ b/frappe/core/doctype/docperm/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/docperm/docperm.py b/frappe/core/doctype/docperm/docperm.py index 36ed9acbe6..9732cde920 100644 --- a/frappe/core/doctype/docperm/docperm.py +++ b/frappe/core/doctype/docperm/docperm.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/docshare/docshare.py b/frappe/core/doctype/docshare/docshare.py index 26ed53a87d..2d7b6b9e48 100644 --- a/frappe/core/doctype/docshare/docshare.py +++ b/frappe/core/doctype/docshare/docshare.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ diff --git a/frappe/core/doctype/docshare/test_docshare.py b/frappe/core/doctype/docshare/test_docshare.py index 9c424eb4d7..6551dabbea 100644 --- a/frappe/core/doctype/docshare/test_docshare.py +++ b/frappe/core/doctype/docshare/test_docshare.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import frappe import frappe.share import unittest diff --git a/frappe/core/doctype/doctype/__init__.py b/frappe/core/doctype/doctype/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/doctype/__init__.py +++ b/frappe/core/doctype/doctype/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 1a173f7252..b4d3fb9a89 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -33,11 +33,11 @@ frappe.ui.form.on('DocType', { if (!frm.is_new() && !frm.doc.istable) { if (frm.doc.issingle) { - frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => { + frm.add_custom_button(__('Go to {0}', [__(frm.doc.name)]), () => { window.open(`/app/${frappe.router.slug(frm.doc.name)}`); }); } else { - frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { + frm.add_custom_button(__('Go to {0} List', [__(frm.doc.name)]), () => { window.open(`/app/${frappe.router.slug(frm.doc.name)}`); }); } diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index f9dbeb0907..3cdc45ea08 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -2,18 +2,12 @@ # MIT License. See license.txt # imports - standard imports -from __future__ import unicode_literals import re, copy, os, shutil import json from frappe.cache_manager import clear_user_cache, clear_controller_cache -# imports - third party imports -import six -from six import iteritems - # imports - module imports import frappe -import frappe.website.render from frappe import _ from frappe.utils import now, cint from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields, data_field_options @@ -28,6 +22,7 @@ from frappe.model.docfield import supports_translation from frappe.modules.import_file import get_file_path from frappe.model.meta import Meta from frappe.desk.utils import validate_route_conflict +from frappe.website.utils import clear_cache class InvalidFieldNameError(frappe.ValidationError): pass class UniqueFieldnameError(frappe.ValidationError): pass @@ -198,7 +193,7 @@ class DocType(Document): self.flags.update_fields_to_fetch_queries = [] - if set(old_fields_to_fetch) != set([df.fieldname for df in new_meta.get_fields_to_fetch()]): + if set(old_fields_to_fetch) != set(df.fieldname for df in new_meta.get_fields_to_fetch()): for df in new_meta.get_fields_to_fetch(): if df.fieldname not in old_fields_to_fetch: link_fieldname, source_fieldname = df.fetch_from.split('.', 1) @@ -253,7 +248,7 @@ class DocType(Document): frappe.throw(_('Field "route" is mandatory for Web Views'), title='Missing Field') # clear website cache - frappe.website.render.clear_cache() + clear_cache() def change_modified_of_parent(self): """Change the timestamp of parent DocType if the current one is a child to clear caches.""" @@ -486,7 +481,7 @@ class DocType(Document): # remove null and empty fields def remove_null_fields(o): to_remove = [] - for attr, value in iteritems(o): + for attr, value in o.items(): if isinstance(value, list): for v in value: remove_null_fields(v) @@ -555,11 +550,6 @@ class DocType(Document): from frappe.modules.export_file import export_to_files export_to_files(record_list=[['DocType', self.name]], create_init=True) - def import_doc(self): - """Import from standard folder `[module]/doctype/[name]/[name].json`.""" - from frappe.modules.import_module import import_from_files - import_from_files(record_list=[[self.module, 'doctype', self.name]]) - def make_controller_template(self): """Make boilerplate controller template.""" make_boilerplate("controller._py", self) @@ -670,7 +660,7 @@ class DocType(Document): if not name: name = self.name - flags = {"flags": re.ASCII} if six.PY3 else {} + flags = {"flags": re.ASCII} # a DocType name should not start or end with an empty space if re.search(r"^[ \t\n\r]+|[ \t\n\r]+$", name, **flags): @@ -767,7 +757,7 @@ def validate_fields(meta): invalid_fields = ('doctype',) if fieldname in invalid_fields: frappe.throw(_("{0}: Fieldname cannot be one of {1}") - .format(docname, ", ".join([frappe.bold(d) for d in invalid_fields]))) + .format(docname, ", ".join(frappe.bold(d) for d in invalid_fields))) def check_unique_fieldname(docname, fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) @@ -1001,7 +991,7 @@ def validate_fields(meta): if docfield.options and (docfield.options not in data_field_options): df_str = frappe.bold(_(docfield.label)) text_str = _("{0} is an invalid Data field.").format(df_str) + "
" * 2 + _("Only Options allowed for Data field are:") + "
" - df_options_str = "
  • " + "
  • ".join([_(x) for x in data_field_options]) + "
" + df_options_str = "
  • " + "
  • ".join(_(x) for x in data_field_options) + "
" frappe.msgprint(text_str + df_options_str, title="Invalid Data Field", raise_exception=True) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 9c492d2c36..1e1a01a685 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.core.doctype.doctype.doctype import (UniqueFieldnameError, diff --git a/frappe/core/doctype/doctype_action/doctype_action.py b/frappe/core/doctype/doctype_action/doctype_action.py index a745c7da40..203b06ec1b 100644 --- a/frappe/core/doctype/doctype_action/doctype_action.py +++ b/frappe/core/doctype/doctype_action/doctype_action.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/doctype_link/doctype_link.py b/frappe/core/doctype/doctype_link/doctype_link.py index efe8b09809..07e0efdace 100644 --- a/frappe/core/doctype/doctype_link/doctype_link.py +++ b/frappe/core/doctype/doctype_link/doctype_link.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.js b/frappe/core/doctype/document_naming_rule/document_naming_rule.js index 56b5c2fdf4..097a4e9a6e 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.js +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.js @@ -4,6 +4,7 @@ frappe.ui.form.on('Document Naming Rule', { refresh: function(frm) { frm.trigger('document_type'); + if (!frm.doc.__islocal) frm.trigger("add_update_counter_button"); }, document_type: (frm) => { // update the select field options with fieldnames @@ -20,5 +21,44 @@ frappe.ui.form.on('Document Naming Rule', { ); }); } + }, + add_update_counter_button: (frm) => { + frm.add_custom_button(__('Update Counter'), function() { + + const fields = [{ + fieldtype: 'Data', + fieldname: 'new_counter', + label: __('New Counter'), + default: frm.doc.counter, + reqd: 1, + description: __('Warning: Updating counter may lead to document name conflicts if not done properly') + }]; + + let primary_action_label = __('Save'); + + let primary_action = (fields) => { + frappe.call({ + method: 'frappe.core.doctype.document_naming_rule.document_naming_rule.update_current', + args: { + name: frm.doc.name, + new_counter: fields.new_counter + }, + callback: function() { + frm.set_value("counter", fields.new_counter); + dialog.hide(); + } + }); + }; + + const dialog = new frappe.ui.Dialog({ + title: __('Update Counter Value for Prefix: {0}', [frm.doc.prefix]), + fields, + primary_action_label, + primary_action + }); + + dialog.show(); + + }); } }); diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.py b/frappe/core/doctype/document_naming_rule/document_naming_rule.py index 4b34293af6..10099bd19a 100644 --- a/frappe/core/doctype/document_naming_rule/document_naming_rule.py +++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.py @@ -2,7 +2,6 @@ # 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 from frappe.utils.data import evaluate_filters @@ -30,3 +29,8 @@ class DocumentNamingRule(Document): counter = frappe.db.get_value(self.doctype, self.name, 'counter', for_update=True) or 0 doc.name = self.prefix + ('%0'+str(self.prefix_digits)+'d') % (counter + 1) frappe.db.set_value(self.doctype, self.name, 'counter', counter + 1) + +@frappe.whitelist() +def update_current(name, new_counter): + frappe.only_for('System Manager') + frappe.db.set_value('Document Naming Rule', name, 'counter', new_counter) diff --git a/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py b/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py index 1b91f6a0cf..2206d173d7 100644 --- a/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py +++ b/frappe/core/doctype/document_naming_rule/test_document_naming_rule.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py b/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py index 0895c9f93f..dfca052d95 100644 --- a/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py +++ b/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py b/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py index 6f1376dc62..643e963bd7 100644 --- a/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py +++ b/frappe/core/doctype/document_naming_rule_condition/test_document_naming_rule_condition.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/domain/domain.py b/frappe/core/doctype/domain/domain.py index a4e9f503ab..bbd20f3b70 100644 --- a/frappe/core/doctype/domain/domain.py +++ b/frappe/core/doctype/domain/domain.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document @@ -111,7 +110,7 @@ class Domain(Document): # enable frappe.db.sql('''update `tabPortal Menu Item` set enabled=1 - where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.allow_sidebar_items]))) + where route in ({0})'''.format(', '.join('"{0}"'.format(d) for d in self.data.allow_sidebar_items))) if self.data.remove_sidebar_items: # disable all @@ -119,4 +118,4 @@ class Domain(Document): # enable frappe.db.sql('''update `tabPortal Menu Item` set enabled=0 - where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.remove_sidebar_items]))) + where route in ({0})'''.format(', '.join('"{0}"'.format(d) for d in self.data.remove_sidebar_items))) diff --git a/frappe/core/doctype/domain/test_domain.py b/frappe/core/doctype/domain/test_domain.py index 8e0bc65c54..c2686a7566 100644 --- a/frappe/core/doctype/domain/test_domain.py +++ b/frappe/core/doctype/domain/test_domain.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/domain_settings/domain_settings.py b/frappe/core/doctype/domain_settings/domain_settings.py index d4d394a5cb..7ad0aeff21 100644 --- a/frappe/core/doctype/domain_settings/domain_settings.py +++ b/frappe/core/doctype/domain_settings/domain_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/dynamic_link/dynamic_link.py b/frappe/core/doctype/dynamic_link/dynamic_link.py index 30e0ef1f1f..a7adb9ae72 100644 --- a/frappe/core/doctype/dynamic_link/dynamic_link.py +++ b/frappe/core/doctype/dynamic_link/dynamic_link.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/error_log/error_log.py b/frappe/core/doctype/error_log/error_log.py index ec02aaf446..8223238c57 100644 --- a/frappe/core/doctype/error_log/error_log.py +++ b/frappe/core/doctype/error_log/error_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/error_log/test_error_log.py b/frappe/core/doctype/error_log/test_error_log.py index d93fe07c61..d7444ab2a7 100644 --- a/frappe/core/doctype/error_log/test_error_log.py +++ b/frappe/core/doctype/error_log/test_error_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/error_snapshot/error_snapshot.py b/frappe/core/doctype/error_snapshot/error_snapshot.py index 5badaad63f..247a796a6b 100644 --- a/frappe/core/doctype/error_snapshot/error_snapshot.py +++ b/frappe/core/doctype/error_snapshot/error_snapshot.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/error_snapshot/test_error_snapshot.py b/frappe/core/doctype/error_snapshot/test_error_snapshot.py index b6438eae1d..135136294a 100644 --- a/frappe/core/doctype/error_snapshot/test_error_snapshot.py +++ b/frappe/core/doctype/error_snapshot/test_error_snapshot.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/patches/v4_0/__init__.py b/frappe/core/doctype/feedback/__init__.py similarity index 100% rename from frappe/patches/v4_0/__init__.py rename to frappe/core/doctype/feedback/__init__.py diff --git a/frappe/core/doctype/feedback/feedback.js b/frappe/core/doctype/feedback/feedback.js new file mode 100644 index 0000000000..131f0e19d8 --- /dev/null +++ b/frappe/core/doctype/feedback/feedback.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Feedback', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/core/doctype/feedback/feedback.json b/frappe/core/doctype/feedback/feedback.json new file mode 100644 index 0000000000..cf8a180e27 --- /dev/null +++ b/frappe/core/doctype/feedback/feedback.json @@ -0,0 +1,86 @@ +{ + "actions": [], + "creation": "2021-06-03 19:02:55.328423", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_doctype", + "reference_name", + "column_break_3", + "email", + "rating", + "section_break_6", + "feedback" + ], + "fields": [ + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "email", + "fieldtype": "Data", + "label": "Email", + "reqd": 1 + }, + { + "fieldname": "rating", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Rating", + "precision": "1", + "reqd": 1 + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "fieldname": "feedback", + "fieldtype": "Small Text", + "label": "Feedback", + "reqd": 1 + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Reference Document Type", + "options": "\nBlog Post" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference Name", + "options": "reference_doctype", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-06-14 15:11:26.005805", + "modified_by": "Administrator", + "module": "Core", + "name": "Feedback", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "reference_name", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/core/doctype/feedback/feedback.py b/frappe/core/doctype/feedback/feedback.py new file mode 100644 index 0000000000..655bed6eb1 --- /dev/null +++ b/frappe/core/doctype/feedback/feedback.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class Feedback(Document): + pass diff --git a/frappe/core/doctype/feedback/test_feedback.py b/frappe/core/doctype/feedback/test_feedback.py new file mode 100644 index 0000000000..702f9d8ac1 --- /dev/null +++ b/frappe/core/doctype/feedback/test_feedback.py @@ -0,0 +1,27 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +import frappe +import unittest + +class TestFeedback(unittest.TestCase): + def test_feedback_creation_updation(self): + from frappe.website.doctype.blog_post.test_blog_post import make_test_blog + test_blog = make_test_blog() + + frappe.db.sql("delete from `tabFeedback` where reference_doctype = 'Blog Post'") + + from frappe.templates.includes.feedback.feedback import add_feedback, update_feedback + feedback = add_feedback('Blog Post', test_blog.name, 5, 'New feedback','test@test.com') + + self.assertEqual(feedback.feedback, 'New feedback') + self.assertEqual(feedback.rating, 5) + + updated_feedback = update_feedback('Blog Post', test_blog.name, 6, 'Updated feedback', 'test@test.com') + + self.assertEqual(updated_feedback.feedback, 'Updated feedback') + self.assertEqual(updated_feedback.rating, 6) + + frappe.db.sql("delete from `tabFeedback` where reference_doctype = 'Blog Post'") + + test_blog.delete() \ No newline at end of file diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index c4c37e6d13..b4bfe1d21b 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -7,8 +7,6 @@ record of files naming for same name files: file.gif, file-1.gif, file-2.gif etc """ -from __future__ import unicode_literals - import base64 import hashlib import imghdr @@ -23,8 +21,8 @@ import zipfile import requests import requests.exceptions from PIL import Image, ImageFile, ImageOps -from six import PY2, StringIO, string_types, text_type -from six.moves.urllib.parse import quote, unquote +from io import StringIO +from urllib.parse import quote, unquote import frappe from frappe import _, conf @@ -382,18 +380,14 @@ class File(Document): file_path = self.get_full_path() # read the file - if PY2: - with open(encode(file_path)) as f: - content = f.read() - else: - with io.open(encode(file_path), mode='rb') as f: - content = f.read() - try: - # for plain text files - content = content.decode() - except UnicodeDecodeError: - # for .png, .jpg, etc - pass + with io.open(encode(file_path), mode='rb') as f: + content = f.read() + try: + # for plain text files + content = content.decode() + except UnicodeDecodeError: + # for .png, .jpg, etc + pass return content @@ -430,7 +424,7 @@ class File(Document): frappe.create_folder(file_path) # write the file self.content = self.get_content() - if isinstance(self.content, text_type): + if isinstance(self.content, str): self.content = self.content.encode() with open(os.path.join(file_path.encode('utf-8'), self.file_name.encode('utf-8')), 'wb+') as f: f.write(self.content) @@ -483,7 +477,7 @@ class File(Document): self.content = content if decode: - if isinstance(content, text_type): + if isinstance(content, str): self.content = content.encode("utf-8") if b"," in self.content: @@ -632,7 +626,7 @@ def create_new_folder(file_name, folder): @frappe.whitelist() def move_file(file_list, new_parent, old_parent): - if isinstance(file_list, string_types): + if isinstance(file_list, str): file_list = json.loads(file_list) for file_obj in file_list: @@ -834,7 +828,7 @@ def remove_file_by_url(file_url, doctype=None, name=None): def get_content_hash(content): - if isinstance(content, text_type): + if isinstance(content, str): content = content.encode() return hashlib.md5(content).hexdigest() #nosec @@ -887,8 +881,8 @@ def extract_images_from_html(doc, content): filename = headers.split("filename=")[-1] # decode filename - if not isinstance(filename, text_type): - filename = text_type(filename, 'utf-8') + if not isinstance(filename, str): + filename = str(filename, 'utf-8') else: mtype = headers.split(";")[0] filename = get_random_filename(content_type=mtype) @@ -911,7 +905,7 @@ def extract_images_from_html(doc, content): return ']*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, content) return content @@ -941,7 +935,7 @@ def get_attached_images(doctype, names): '''get list of image urls attached in form returns {name: ['image.jpg', 'image.png']}''' - if isinstance(names, string_types): + if isinstance(names, str): names = json.loads(names) img_urls = frappe.db.get_list('File', filters={ diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py index 2596fe94d0..649010c468 100644 --- a/frappe/core/doctype/file/test_file.py +++ b/frappe/core/doctype/file/test_file.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import base64 import frappe import os diff --git a/frappe/core/doctype/has_domain/has_domain.py b/frappe/core/doctype/has_domain/has_domain.py index 6381996035..2220656a2e 100644 --- a/frappe/core/doctype/has_domain/has_domain.py +++ b/frappe/core/doctype/has_domain/has_domain.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/has_role/has_role.py b/frappe/core/doctype/has_role/has_role.py index 45e76c85a1..51d86c7b0a 100644 --- a/frappe/core/doctype/has_role/has_role.py +++ b/frappe/core/doctype/has_role/has_role.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/installed_application/installed_application.py b/frappe/core/doctype/installed_application/installed_application.py index 6bb12afc49..f53a6424eb 100644 --- a/frappe/core/doctype/installed_application/installed_application.py +++ b/frappe/core/doctype/installed_application/installed_application.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/installed_applications/installed_applications.py b/frappe/core/doctype/installed_applications/installed_applications.py index 4e6eadf07e..b61555f57e 100644 --- a/frappe/core/doctype/installed_applications/installed_applications.py +++ b/frappe/core/doctype/installed_applications/installed_applications.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/installed_applications/test_installed_applications.py b/frappe/core/doctype/installed_applications/test_installed_applications.py index ab9b849fa1..1d57fd2cd8 100644 --- a/frappe/core/doctype/installed_applications/test_installed_applications.py +++ b/frappe/core/doctype/installed_applications/test_installed_applications.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/language/language.py b/frappe/core/doctype/language/language.py index fb18abdf5e..01c8553e10 100644 --- a/frappe/core/doctype/language/language.py +++ b/frappe/core/doctype/language/language.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json, re from frappe import _ from frappe.model.document import Document diff --git a/frappe/core/doctype/language/test_language.py b/frappe/core/doctype/language/test_language.py index a4f35dd77b..837594247f 100644 --- a/frappe/core/doctype/language/test_language.py +++ b/frappe/core/doctype/language/test_language.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/log_setting_user/log_setting_user.py b/frappe/core/doctype/log_setting_user/log_setting_user.py index df6d55f0a9..64728b2c2b 100644 --- a/frappe/core/doctype/log_setting_user/log_setting_user.py +++ b/frappe/core/doctype/log_setting_user/log_setting_user.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/log_setting_user/test_log_setting_user.py b/frappe/core/doctype/log_setting_user/test_log_setting_user.py index 507c02d87d..c58b8faa66 100644 --- a/frappe/core/doctype/log_setting_user/test_log_setting_user.py +++ b/frappe/core/doctype/log_setting_user/test_log_setting_user.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py index 08e61d3289..e73aa8dac1 100644 --- a/frappe/core/doctype/log_settings/log_settings.py +++ b/frappe/core/doctype/log_settings/log_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document diff --git a/frappe/core/doctype/log_settings/test_log_settings.py b/frappe/core/doctype/log_settings/test_log_settings.py index 2824c71c88..8e0c9c3f23 100644 --- a/frappe/core/doctype/log_settings/test_log_settings.py +++ b/frappe/core/doctype/log_settings/test_log_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/module_def/__init__.py b/frappe/core/doctype/module_def/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/module_def/__init__.py +++ b/frappe/core/doctype/module_def/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/module_def/module_def.json b/frappe/core/doctype/module_def/module_def.json index 7a8bfd76a7..4de046bbb6 100644 --- a/frappe/core/doctype/module_def/module_def.json +++ b/frappe/core/doctype/module_def/module_def.json @@ -55,7 +55,7 @@ "link_fieldname": "module" } ], - "modified": "2020-08-06 12:39:30.740379", + "modified": "2021-06-02 13:04:53.118716", "modified_by": "Administrator", "module": "Core", "name": "Module Def", @@ -69,6 +69,7 @@ "read": 1, "report": 1, "role": "Administrator", + "select": 1, "share": 1, "write": 1 }, @@ -78,7 +79,14 @@ "read": 1, "report": 1, "role": "System Manager", + "select": 1, "write": 1 + }, + { + "read": 1, + "report": 1, + "role": "All", + "select": 1 } ], "show_name_in_global_search": 1, diff --git a/frappe/core/doctype/module_def/module_def.py b/frappe/core/doctype/module_def/module_def.py index 7e63572162..68025c83bb 100644 --- a/frappe/core/doctype/module_def/module_def.py +++ b/frappe/core/doctype/module_def/module_def.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, os, json from frappe.model.document import Document diff --git a/frappe/core/doctype/module_def/test_module_def.py b/frappe/core/doctype/module_def/test_module_def.py index 1f9bea4768..3a3ceb4b57 100644 --- a/frappe/core/doctype/module_def/test_module_def.py +++ b/frappe/core/doctype/module_def/test_module_def.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/module_profile/module_profile.py b/frappe/core/doctype/module_profile/module_profile.py index 4f392353ac..373e5078d0 100644 --- a/frappe/core/doctype/module_profile/module_profile.py +++ b/frappe/core/doctype/module_profile/module_profile.py @@ -2,7 +2,6 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class ModuleProfile(Document): diff --git a/frappe/core/doctype/module_profile/test_module_profile.py b/frappe/core/doctype/module_profile/test_module_profile.py index 400053d22c..e0d9c13371 100644 --- a/frappe/core/doctype/module_profile/test_module_profile.py +++ b/frappe/core/doctype/module_profile/test_module_profile.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals import frappe import unittest diff --git a/frappe/core/doctype/navbar_item/navbar_item.py b/frappe/core/doctype/navbar_item/navbar_item.py index 614aee8eaf..a8fa611374 100644 --- a/frappe/core/doctype/navbar_item/navbar_item.py +++ b/frappe/core/doctype/navbar_item/navbar_item.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/navbar_item/test_navbar_item.py b/frappe/core/doctype/navbar_item/test_navbar_item.py index 192e8fe42a..85852a45e8 100644 --- a/frappe/core/doctype/navbar_item/test_navbar_item.py +++ b/frappe/core/doctype/navbar_item/test_navbar_item.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index 2244bc9e4e..60aec67a00 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -2,7 +2,6 @@ # 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 from frappe import _ diff --git a/frappe/core/doctype/navbar_settings/test_navbar_settings.py b/frappe/core/doctype/navbar_settings/test_navbar_settings.py index ed423b0f27..4d1ee72815 100644 --- a/frappe/core/doctype/navbar_settings/test_navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/test_navbar_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/page/__init__.py b/frappe/core/doctype/page/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/page/__init__.py +++ b/frappe/core/doctype/page/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index bdec350efd..0ba0e309dd 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import os from frappe.model.document import Document @@ -11,7 +10,6 @@ from frappe import conf, _, safe_decode from frappe.desk.form.meta import get_code_files_via_hooks, get_js from frappe.desk.utils import validate_route_conflict from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles -from six import text_type class Page(Document): def autoname(self): diff --git a/frappe/core/doctype/page/test_page.py b/frappe/core/doctype/page/test_page.py index f7b3952a5b..18b4aea2c8 100644 --- a/frappe/core/doctype/page/test_page.py +++ b/frappe/core/doctype/page/test_page.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/patch_log/patch_log.py b/frappe/core/doctype/patch_log/patch_log.py index 3103d44af4..cc66955eb8 100644 --- a/frappe/core/doctype/patch_log/patch_log.py +++ b/frappe/core/doctype/patch_log/patch_log.py @@ -3,7 +3,6 @@ # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/patch_log/test_patch_log.py b/frappe/core/doctype/patch_log/test_patch_log.py index 0a7f22a78b..d0690ecee0 100644 --- a/frappe/core/doctype/patch_log/test_patch_log.py +++ b/frappe/core/doctype/patch_log/test_patch_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/payment_gateway/payment_gateway.py b/frappe/core/doctype/payment_gateway/payment_gateway.py index 80799e311b..1459635b01 100644 --- a/frappe/core/doctype/payment_gateway/payment_gateway.py +++ b/frappe/core/doctype/payment_gateway/payment_gateway.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/payment_gateway/test_payment_gateway.py b/frappe/core/doctype/payment_gateway/test_payment_gateway.py index 2faf1a7fb4..66f899bd27 100644 --- a/frappe/core/doctype/payment_gateway/test_payment_gateway.py +++ b/frappe/core/doctype/payment_gateway/test_payment_gateway.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index c27853f460..c68bb6a4f1 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -3,8 +3,6 @@ # For license information, please see license.txt -from __future__ import unicode_literals - import json import frappe diff --git a/frappe/core/doctype/prepared_report/test_prepared_report.py b/frappe/core/doctype/prepared_report/test_prepared_report.py index 17845be521..ef324dd01a 100644 --- a/frappe/core/doctype/prepared_report/test_prepared_report.py +++ b/frappe/core/doctype/prepared_report/test_prepared_report.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest import json diff --git a/frappe/core/doctype/report/__init__.py b/frappe/core/doctype/report/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/report/__init__.py +++ b/frappe/core/doctype/report/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 8a0f9a99f5..a5c61fa436 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals import frappe import json, datetime from frappe import _, scrub @@ -13,7 +11,6 @@ from frappe.modules import make_boilerplate from frappe.core.doctype.page.page import delete_custom_role from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles from frappe.desk.reportview import append_totals_row -from six import iteritems from frappe.utils.safe_exec import safe_exec @@ -238,7 +235,7 @@ class Report(Document): _filters = params.get('filters') or [] if filters: - for key, value in iteritems(filters): + for key, value in filters.items(): condition, _value = '=', value if isinstance(value, (list, tuple)): condition, _value = value diff --git a/frappe/core/doctype/report/test_report.py b/frappe/core/doctype/report/test_report.py index d09799ca69..9d0c0b9af0 100644 --- a/frappe/core/doctype/report/test_report.py +++ b/frappe/core/doctype/report/test_report.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import frappe, json, os import unittest from frappe.desk.query_report import run, save_report diff --git a/frappe/core/doctype/report_column/report_column.py b/frappe/core/doctype/report_column/report_column.py index 69c88b7bda..f9078d820d 100644 --- a/frappe/core/doctype/report_column/report_column.py +++ b/frappe/core/doctype/report_column/report_column.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/report_filter/report_filter.py b/frappe/core/doctype/report_filter/report_filter.py index d85a1a5a65..ccdcc0eb6f 100644 --- a/frappe/core/doctype/report_filter/report_filter.py +++ b/frappe/core/doctype/report_filter/report_filter.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/core/doctype/role/__init__.py b/frappe/core/doctype/role/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/doctype/role/__init__.py +++ b/frappe/core/doctype/role/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index a1523db0dd..02482c75ca 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/role/test_role.py b/frappe/core/doctype/role/test_role.py index 6459a72c98..471f6cac43 100644 --- a/frappe/core/doctype/role/test_role.py +++ b/frappe/core/doctype/role/test_role.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py index 77b523987c..59f34a1483 100644 --- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py +++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.core.doctype.report.report import is_prepared_report_disabled from frappe.model.document import Document diff --git a/frappe/core/doctype/role_profile/role_profile.py b/frappe/core/doctype/role_profile/role_profile.py index 4def834adb..0f58da5b5e 100644 --- a/frappe/core/doctype/role_profile/role_profile.py +++ b/frappe/core/doctype/role_profile/role_profile.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class RoleProfile(Document): diff --git a/frappe/core/doctype/role_profile/test_role_profile.py b/frappe/core/doctype/role_profile/test_role_profile.py index 975453e8d1..53e0a1b043 100644 --- a/frappe/core/doctype/role_profile/test_role_profile.py +++ b/frappe/core/doctype/role_profile/test_role_profile.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals import frappe import unittest diff --git a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py index 26871c9adf..7f54a3b6ae 100644 --- a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py +++ b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py b/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py index 1e5290425b..85471d0d71 100644 --- a/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py +++ b/frappe/core/doctype/scheduled_job_log/test_scheduled_job_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py index d0a65defa4..a071cfe9a9 100644 --- a/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/test_scheduled_job_type.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.utils import get_datetime diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index f80a067cf1..d26fe5a188 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -2,8 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import ast from types import FunctionType, MethodType, ModuleType from typing import Dict, List diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index aac8b3deed..c39fcfa0d0 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest import requests diff --git a/frappe/core/doctype/session_default/session_default.py b/frappe/core/doctype/session_default/session_default.py index 8a8db46ff1..70ff103111 100644 --- a/frappe/core/doctype/session_default/session_default.py +++ b/frappe/core/doctype/session_default/session_default.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/session_default_settings/session_default_settings.py b/frappe/core/doctype/session_default_settings/session_default_settings.py index 7b4bd19e9a..25f7522c86 100644 --- a/frappe/core/doctype/session_default_settings/session_default_settings.py +++ b/frappe/core/doctype/session_default_settings/session_default_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ import json diff --git a/frappe/core/doctype/session_default_settings/test_session_default_settings.py b/frappe/core/doctype/session_default_settings/test_session_default_settings.py index 12aa14d343..7d20015b66 100644 --- a/frappe/core/doctype/session_default_settings/test_session_default_settings.py +++ b/frappe/core/doctype/session_default_settings/test_session_default_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.core.doctype.session_default_settings.session_default_settings import set_session_default_values, clear_session_defaults diff --git a/frappe/core/doctype/sms_parameter/__init__.py b/frappe/core/doctype/sms_parameter/__init__.py index baffc48825..8b13789179 100755 --- a/frappe/core/doctype/sms_parameter/__init__.py +++ b/frappe/core/doctype/sms_parameter/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals + diff --git a/frappe/core/doctype/sms_parameter/sms_parameter.py b/frappe/core/doctype/sms_parameter/sms_parameter.py index 08b220b61a..d1fb1c53db 100644 --- a/frappe/core/doctype/sms_parameter/sms_parameter.py +++ b/frappe/core/doctype/sms_parameter/sms_parameter.py @@ -1,7 +1,6 @@ # 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.model.document import Document diff --git a/frappe/core/doctype/sms_settings/__init__.py b/frappe/core/doctype/sms_settings/__init__.py index baffc48825..8b13789179 100755 --- a/frappe/core/doctype/sms_settings/__init__.py +++ b/frappe/core/doctype/sms_settings/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals + diff --git a/frappe/core/doctype/sms_settings/sms_settings.py b/frappe/core/doctype/sms_settings/sms_settings.py index ac835108c1..58a0ff08f6 100644 --- a/frappe/core/doctype/sms_settings/sms_settings.py +++ b/frappe/core/doctype/sms_settings/sms_settings.py @@ -2,15 +2,12 @@ # 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 _, throw, msgprint from frappe.utils import nowdate from frappe.model.document import Document -import six -from six import string_types class SMSSettings(Document): pass @@ -35,20 +32,20 @@ def validate_receiver_nos(receiver_list): @frappe.whitelist() def get_contact_number(contact_name, ref_doctype, ref_name): "returns mobile number of the contact" - number = frappe.db.sql("""select mobile_no, phone from tabContact - where name=%s + number = frappe.db.sql("""select mobile_no, phone from tabContact + where name=%s and exists( select name from `tabDynamic Link` where link_doctype=%s and link_name=%s ) """, (contact_name, ref_doctype, ref_name)) - + return number and (number[0][0] or number[0][1]) or '' @frappe.whitelist() def send_sms(receiver_list, msg, sender_name = '', success_msg = True): import json - if isinstance(receiver_list, string_types): + if isinstance(receiver_list, str): receiver_list = json.loads(receiver_list) if not isinstance(receiver_list, list): receiver_list = [receiver_list] diff --git a/frappe/core/doctype/sms_settings/test_sms_settings.py b/frappe/core/doctype/sms_settings/test_sms_settings.py index b14fd3e4a0..862f5e3965 100644 --- a/frappe/core/doctype/sms_settings/test_sms_settings.py +++ b/frappe/core/doctype/sms_settings/test_sms_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/success_action/success_action.py b/frappe/core/doctype/success_action/success_action.py index f8b99f1fea..4ebd3d250b 100644 --- a/frappe/core/doctype/success_action/success_action.py +++ b/frappe/core/doctype/success_action/success_action.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class SuccessAction(Document): diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 05aaca81de..466914569f 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document diff --git a/frappe/core/doctype/system_settings/test_system_settings.py b/frappe/core/doctype/system_settings/test_system_settings.py index 82d0ddbd7c..a65c602abe 100644 --- a/frappe/core/doctype/system_settings/test_system_settings.py +++ b/frappe/core/doctype/system_settings/test_system_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py index 7e91b1cd4a..98e36e6a30 100644 --- a/frappe/core/doctype/test/test.py +++ b/frappe/core/doctype/test/test.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document import json diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py index 2a9b43bf95..d8ca975d63 100644 --- a/frappe/core/doctype/test/test_test.py +++ b/frappe/core/doctype/test/test_test.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/transaction_log/test_transaction_log.py b/frappe/core/doctype/transaction_log/test_transaction_log.py index 164a683c38..0d9b9353d0 100644 --- a/frappe/core/doctype/transaction_log/test_transaction_log.py +++ b/frappe/core/doctype/transaction_log/test_transaction_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest import hashlib @@ -35,7 +33,7 @@ class TestTransactionLog(unittest.TestCase): sha = hashlib.sha256() sha.update( - frappe.safe_encode(str(third_log.transaction_hash)) + + frappe.safe_encode(str(third_log.transaction_hash)) + frappe.safe_encode(str(second_log.chaining_hash)) ) diff --git a/frappe/core/doctype/transaction_log/transaction_log.py b/frappe/core/doctype/transaction_log/transaction_log.py index b7ea6cac60..58d0b3d176 100644 --- a/frappe/core/doctype/transaction_log/transaction_log.py +++ b/frappe/core/doctype/transaction_log/transaction_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document diff --git a/frappe/core/doctype/translation/test_translation.py b/frappe/core/doctype/translation/test_translation.py index 12899dddf7..ae1293b38f 100644 --- a/frappe/core/doctype/translation/test_translation.py +++ b/frappe/core/doctype/translation/test_translation.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/core/doctype/translation/translation.py b/frappe/core/doctype/translation/translation.py index 177dea401f..b1f4642791 100644 --- a/frappe/core/doctype/translation/translation.py +++ b/frappe/core/doctype/translation/translation.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils import strip_html_tags, is_html diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 5bea767934..392128834d 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest, uuid from frappe.model.delete_doc import delete_doc diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index a4d13a57e0..5b605504e8 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -1,10 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function - from bs4 import BeautifulSoup - import frappe import frappe.share import frappe.defaults @@ -17,7 +13,7 @@ from frappe.utils.password import update_password as _update_password, check_pas from frappe.desk.notifications import clear_notifications from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings, toggle_notifications from frappe.utils.user import get_system_managers -from frappe.website.utils import is_signup_enabled +from frappe.website.utils import is_signup_disabled from frappe.rate_limiter import rate_limit from frappe.utils.background_jobs import enqueue from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype @@ -843,7 +839,7 @@ def verify_password(password): @frappe.whitelist(allow_guest=True) def sign_up(email, full_name, redirect_to): - if not is_signup_enabled(): + if is_signup_disabled(): frappe.throw(_('Sign Up is disabled'), title='Not Allowed') user = frappe.db.get("User", {"email": email}) @@ -935,7 +931,7 @@ def user_query(doctype, txt, searchfield, start, page_len, filters): LIMIT %(page_len)s OFFSET %(start)s """.format( user_type_condition = user_type_condition, - standard_users=", ".join([frappe.db.escape(u) for u in STANDARD_USERS]), + standard_users=", ".join(frappe.db.escape(u) for u in STANDARD_USERS), key=searchfield, fcond=get_filters_cond(doctype, filters, conditions), mcond=get_match_cond(doctype) diff --git a/frappe/core/doctype/user_document_type/user_document_type.py b/frappe/core/doctype/user_document_type/user_document_type.py index 979bfcb250..48dbf87b3d 100644 --- a/frappe/core/doctype/user_document_type/user_document_type.py +++ b/frappe/core/doctype/user_document_type/user_document_type.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_email/user_email.py b/frappe/core/doctype/user_email/user_email.py index a0ce2e169d..729aa03444 100644 --- a/frappe/core/doctype/user_email/user_email.py +++ b/frappe/core/doctype/user_email/user_email.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_group/test_user_group.py b/frappe/core/doctype/user_group/test_user_group.py index c7e28f3d31..2f89d032e1 100644 --- a/frappe/core/doctype/user_group/test_user_group.py +++ b/frappe/core/doctype/user_group/test_user_group.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/user_group/user_group.py b/frappe/core/doctype/user_group/user_group.py index b1d0fede4c..178775d407 100644 --- a/frappe/core/doctype/user_group/user_group.py +++ b/frappe/core/doctype/user_group/user_group.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document import frappe diff --git a/frappe/core/doctype/user_group_member/test_user_group_member.py b/frappe/core/doctype/user_group_member/test_user_group_member.py index 38aade4608..8dbaed9e65 100644 --- a/frappe/core/doctype/user_group_member/test_user_group_member.py +++ b/frappe/core/doctype/user_group_member/test_user_group_member.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/user_group_member/user_group_member.py b/frappe/core/doctype/user_group_member/user_group_member.py index 4d0656913d..f85ddc3209 100644 --- a/frappe/core/doctype/user_group_member/user_group_member.py +++ b/frappe/core/doctype/user_group_member/user_group_member.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py index 47651fee72..1a442b53e7 100644 --- a/frappe/core/doctype/user_permission/test_user_permission.py +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals from frappe.core.doctype.user_permission.user_permission import add_user_permissions, remove_applicable from frappe.permissions import has_user_permission from frappe.core.doctype.doctype.test_doctype import new_doctype diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index fec5019ca9..4aa5797c7f 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json from frappe.model.document import Document from frappe.permissions import (get_valid_perms, update_permission_property) @@ -17,11 +16,11 @@ class UserPermission(Document): self.validate_default_permission() def on_update(self): - frappe.cache().delete_value('user_permissions') + frappe.cache().hdel('user_permissions', self.user) frappe.publish_realtime('update_user_permissions') def on_trash(self): # pylint: disable=no-self-use - frappe.cache().delete_value('user_permissions') + frappe.cache().hdel('user_permissions', self.user) frappe.publish_realtime('update_user_permissions') def validate_user_permission(self): diff --git a/frappe/core/doctype/user_select_document_type/user_select_document_type.py b/frappe/core/doctype/user_select_document_type/user_select_document_type.py index 373eaf7aa3..13e3f0d351 100644 --- a/frappe/core/doctype/user_select_document_type/user_select_document_type.py +++ b/frappe/core/doctype/user_select_document_type/user_select_document_type.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/user_social_login/user_social_login.py b/frappe/core/doctype/user_social_login/user_social_login.py index cc6c3d0e05..4a34006d2b 100644 --- a/frappe/core/doctype/user_social_login/user_social_login.py +++ b/frappe/core/doctype/user_social_login/user_social_login.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class UserSocialLogin(Document): diff --git a/frappe/core/doctype/user_type/test_user_type.py b/frappe/core/doctype/user_type/test_user_type.py index de61e0f476..1c47f02bbb 100644 --- a/frappe/core/doctype/user_type/test_user_type.py +++ b/frappe/core/doctype/user_type/test_user_type.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/core/doctype/user_type/user_type.py b/frappe/core/doctype/user_type/user_type.py index 0e8b692416..82ffb090f1 100644 --- a/frappe/core/doctype/user_type/user_type.py +++ b/frappe/core/doctype/user_type/user_type.py @@ -2,10 +2,8 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ -from six import iteritems from frappe.utils import get_link_to_form from frappe.config import get_modules_from_app from frappe.permissions import add_permission, add_user_permission @@ -114,7 +112,7 @@ class UserType(Document): self.select_doctypes = [] select_doctypes = [] - user_doctypes = tuple([row.document_type for row in self.user_doctypes]) + user_doctypes = [row.document_type for row in self.user_doctypes] for doctype in user_doctypes: doc = frappe.get_meta(doctype) @@ -247,7 +245,7 @@ def apply_permissions_for_non_standard_user_type(doc, method=None): if not user_types: return - for user_type, data in iteritems(user_types): + for user_type, data in user_types.items(): if (not doc.get(data[1]) or doc.doctype != data[0]): continue @@ -267,4 +265,4 @@ def apply_permissions_for_non_standard_user_type(doc, method=None): user_doc.update_children() add_user_permission(doc.doctype, doc.name, doc.get(data[1])) else: - frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1])) \ No newline at end of file + frappe.db.set_value('User Permission', perm_data[0], 'user', doc.get(data[1])) diff --git a/frappe/core/doctype/user_type/user_type_dashboard.py b/frappe/core/doctype/user_type/user_type_dashboard.py index 7e14198bca..6cdd2f82a5 100644 --- a/frappe/core/doctype/user_type/user_type_dashboard.py +++ b/frappe/core/doctype/user_type/user_type_dashboard.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + from frappe import _ def get_data(): diff --git a/frappe/core/doctype/user_type_module/user_type_module.py b/frappe/core/doctype/user_type_module/user_type_module.py index 6cd2cbacdb..9afbcd294d 100644 --- a/frappe/core/doctype/user_type_module/user_type_module.py +++ b/frappe/core/doctype/user_type_module/user_type_module.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index 51b3c21f58..f6c099c4ea 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest, copy from frappe.test_runner import make_test_objects diff --git a/frappe/core/doctype/version/version.py b/frappe/core/doctype/version/version.py index 7654db4ae5..a1bd851346 100644 --- a/frappe/core/doctype/version/version.py +++ b/frappe/core/doctype/version/version.py @@ -3,7 +3,6 @@ # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json from frappe.model.document import Document diff --git a/frappe/core/doctype/view_log/test_view_log.py b/frappe/core/doctype/view_log/test_view_log.py index 83967a39a4..025f3d8ad9 100644 --- a/frappe/core/doctype/view_log/test_view_log.py +++ b/frappe/core/doctype/view_log/test_view_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest @@ -25,11 +23,11 @@ class TestViewLog(unittest.TestCase): # load the form getdoc('Event', ev.name) a = frappe.get_value( - doctype="View Log", + doctype="View Log", filters={ "reference_doctype": "Event", "reference_name": ev.name - }, + }, fieldname=['viewed_by'] ) diff --git a/frappe/core/doctype/view_log/view_log.py b/frappe/core/doctype/view_log/view_log.py index 45e98e37c7..242250be8b 100644 --- a/frappe/core/doctype/view_log/view_log.py +++ b/frappe/core/doctype/view_log/view_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/core/notifications.py b/frappe/core/notifications.py index 771a15a2e7..707de43f28 100644 --- a/frappe/core/notifications.py +++ b/frappe/core/notifications.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def get_notification_config(): diff --git a/frappe/core/page/__init__.py b/frappe/core/page/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/core/page/__init__.py +++ b/frappe/core/page/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 1c215eb6e1..15c7cb55ae 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ import frappe.defaults diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py index c928939119..13602ca777 100644 --- a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _, throw import frappe.utils.user diff --git a/frappe/core/report/transaction_log_report/transaction_log_report.py b/frappe/core/report/transaction_log_report/transaction_log_report.py index 9d84901f22..ff8d8345d6 100644 --- a/frappe/core/report/transaction_log_report/transaction_log_report.py +++ b/frappe/core/report/transaction_log_report/transaction_log_report.py @@ -1,7 +1,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import hashlib from frappe import _ diff --git a/frappe/core/utils.py b/frappe/core/utils.py index 55cfbc34d7..9b8ee3a326 100644 --- a/frappe/core/utils.py +++ b/frappe/core/utils.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe diff --git a/frappe/core/web_form/edit_profile/edit_profile.py b/frappe/core/web_form/edit_profile/edit_profile.py index 2334f8b26d..e1ada61927 100644 --- a/frappe/core/web_form/edit_profile/edit_profile.py +++ b/frappe/core/web_form/edit_profile/edit_profile.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe def get_context(context): diff --git a/frappe/custom/doctype/client_script/__init__.py b/frappe/custom/doctype/client_script/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/custom/doctype/client_script/__init__.py +++ b/frappe/custom/doctype/client_script/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/custom/doctype/client_script/client_script.py b/frappe/custom/doctype/client_script/client_script.py index 049f979263..9c098fe8c9 100644 --- a/frappe/custom/doctype/client_script/client_script.py +++ b/frappe/custom/doctype/client_script/client_script.py @@ -1,6 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ diff --git a/frappe/custom/doctype/client_script/test_client_script.py b/frappe/custom/doctype/client_script/test_client_script.py index de113c1ce7..b8358468b9 100644 --- a/frappe/custom/doctype/client_script/test_client_script.py +++ b/frappe/custom/doctype/client_script/test_client_script.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/custom/doctype/custom_field/__init__.py b/frappe/custom/doctype/custom_field/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/custom/doctype/custom_field/__init__.py +++ b/frappe/custom/doctype/custom_field/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 39aff8b4a7..7e6ea1875a 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import json from frappe.utils import cstr diff --git a/frappe/custom/doctype/custom_field/test_custom_field.py b/frappe/custom/doctype/custom_field/test_custom_field.py index 819917050a..3196b66ee8 100644 --- a/frappe/custom/doctype/custom_field/test_custom_field.py +++ b/frappe/custom/doctype/custom_field/test_custom_field.py @@ -3,8 +3,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/custom/doctype/customize_form/__init__.py b/frappe/custom/doctype/customize_form/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/custom/doctype/customize_form/__init__.py +++ b/frappe/custom/doctype/customize_form/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index d9d8ae196e..4e00456f0d 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -117,7 +117,7 @@ frappe.ui.form.on("Customize Form", { frappe.customize_form.set_primary_action(frm); frm.add_custom_button( - __("Go to {0} List", [frm.doc.doc_type]), + __("Go to {0} List", [__(frm.doc.doc_type)]), function() { frappe.set_route("List", frm.doc.doc_type); }, diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 1807678673..c2940a92e3 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -31,7 +31,6 @@ "default_print_format", "column_break_29", "show_preview_popup", - "image_view", "email_settings_section", "default_email_template", "column_break_26", @@ -109,13 +108,6 @@ "fieldtype": "Check", "label": "Track Changes" }, - { - "default": "0", - "depends_on": "eval: doc.image_field", - "fieldname": "image_view", - "fieldtype": "Check", - "label": "Image View" - }, { "fieldname": "column_break_5", "fieldtype": "Column Break" @@ -288,16 +280,6 @@ "fieldname": "autoname", "fieldtype": "Data", "label": "Auto Name" - }, - { - "fieldname": "default_email_template", - "fieldtype": "Link", - "label": "Default Email Template", - "options": "Email Template" - }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" } ], "hide_toolbar": 1, @@ -306,7 +288,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-04-29 21:21:06.476372", + "modified": "2021-06-21 19:01:06.920663", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index be0dded99c..1b8977acc4 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals """ Customize Form is a Single DocType used to mask the Property Setter Thus providing a better UI from user perspective @@ -356,9 +355,9 @@ class CustomizeForm(Document): def delete_custom_fields(self): meta = frappe.get_meta(self.doc_type) - fields_to_remove = (set([df.fieldname for df in meta.get("fields")]) - - set(df.fieldname for df in self.get("fields"))) - + fields_to_remove = ( + {df.fieldname for df in meta.get("fields")} - {df.fieldname for df in self.get("fields")} + ) for fieldname in fields_to_remove: df = meta.get("fields", {"fieldname": fieldname})[0] if df.get("is_custom_field"): diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 75555a8205..58bdcf9a18 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, unittest, json from frappe.test_runner import make_test_records_for_doctype from frappe.core.doctype.doctype.doctype import InvalidFieldNameError diff --git a/frappe/custom/doctype/customize_form_field/__init__.py b/frappe/custom/doctype/customize_form_field/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/custom/doctype/customize_form_field/__init__.py +++ b/frappe/custom/doctype/customize_form_field/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.py b/frappe/custom/doctype/customize_form_field/customize_form_field.py index 20c206328c..f288e70754 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.py +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.js b/frappe/custom/doctype/doctype_layout/doctype_layout.js index 679330e065..533efea9b8 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.js +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.js @@ -23,7 +23,7 @@ frappe.ui.form.on('DocType Layout', { set_button(frm) { if (!frm.is_new()) { frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { - window.open(`/app/list/${frappe.router.slug(frm.doc.name)}/list`); + window.open(`/app/${frappe.router.slug(frm.doc.name)}`); }); } } diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.py b/frappe/custom/doctype/doctype_layout/doctype_layout.py index a4fe9a9bce..0dc320353d 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.py @@ -2,8 +2,6 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - from frappe.model.document import Document from frappe.desk.utils import slug diff --git a/frappe/custom/doctype/doctype_layout/test_doctype_layout.py b/frappe/custom/doctype/doctype_layout/test_doctype_layout.py index 5765c86262..dcde3c00a4 100644 --- a/frappe/custom/doctype/doctype_layout/test_doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/test_doctype_layout.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json index a1a36216c3..006c01ae4e 100644 --- a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json +++ b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json @@ -20,14 +20,13 @@ "fieldname": "label", "fieldtype": "Data", "in_list_view": 1, - "label": "Label", - "reqd": 1 + "label": "Label" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-16 17:13:01.892345", + "modified": "2021-05-19 16:27:40.585865", "modified_by": "Administrator", "module": "Custom", "name": "DocType Layout Field", @@ -36,4 +35,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py index 7f8c8edfce..c1e963602f 100644 --- a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py +++ b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/custom/doctype/property_setter/__init__.py b/frappe/custom/doctype/property_setter/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/custom/doctype/property_setter/__init__.py +++ b/frappe/custom/doctype/property_setter/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 56e5829271..2a6c06b70a 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ diff --git a/frappe/custom/doctype/property_setter/test_property_setter.py b/frappe/custom/doctype/property_setter/test_property_setter.py index 33e7d288a4..4d4de66d51 100644 --- a/frappe/custom/doctype/property_setter/test_property_setter.py +++ b/frappe/custom/doctype/property_setter/test_property_setter.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_rename_new.py index aa5984e466..32d2396b2b 100644 --- a/frappe/custom/doctype/test_rename_new/test_rename_new.py +++ b/frappe/custom/doctype/test_rename_new/test_rename_new.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py index 554efbae45..b3ea4818de 100644 --- a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py +++ b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2021, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/base.py b/frappe/data_migration/doctype/data_migration_connector/connectors/base.py index 97f9f5f4a3..5eca7cfac5 100644 --- a/frappe/data_migration/doctype/data_migration_connector/connectors/base.py +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/base.py @@ -1,10 +1,7 @@ -from __future__ import unicode_literals -from six import with_metaclass from abc import ABCMeta, abstractmethod from frappe.utils.password import get_decrypted_password -class BaseConnection(with_metaclass(ABCMeta)): - +class BaseConnection(metaclass=ABCMeta): @abstractmethod def get(self, remote_objectname, fields=None, filters=None, start=0, page_length=10): pass diff --git a/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py b/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py index 6ee41afdf2..473a15c2dc 100644 --- a/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py +++ b/frappe/data_migration/doctype/data_migration_connector/connectors/frappe_connection.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.frappeclient import FrappeClient from .base import BaseConnection diff --git a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py index 793dfe6694..d1137f2e67 100644 --- a/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py +++ b/frappe/data_migration/doctype/data_migration_connector/data_migration_connector.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, os from frappe.model.document import Document from frappe import _ @@ -76,8 +75,7 @@ def get_connection_class(python_module): return _class -connection_boilerplate = """from __future__ import unicode_literals -from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection +connection_boilerplate = """from frappe.data_migration.doctype.data_migration_connector.connectors.base import BaseConnection class {connection_class}(BaseConnection): def __init__(self, connector): diff --git a/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py index a6e30fbe44..fd45f86ec1 100644 --- a/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py +++ b/frappe/data_migration/doctype/data_migration_connector/test_data_migration_connector.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals import unittest class TestDataMigrationConnector(unittest.TestCase): diff --git a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py index 1cc54a0d1a..5cb20ba56c 100644 --- a/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py +++ b/frappe/data_migration/doctype/data_migration_mapping/data_migration_mapping.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils.safe_exec import get_safe_globals diff --git a/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py index e6f0ce2796..df11fc0522 100644 --- a/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py +++ b/frappe/data_migration/doctype/data_migration_mapping/test_data_migration_mapping.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals import unittest class TestDataMigrationMapping(unittest.TestCase): diff --git a/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py index 1ccdf76eed..6d3ef50937 100644 --- a/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py +++ b/frappe/data_migration/doctype/data_migration_mapping_detail/data_migration_mapping_detail.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class DataMigrationMappingDetail(Document): diff --git a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py index 5cd195f4fe..a8d0e40a4c 100644 --- a/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py +++ b/frappe/data_migration/doctype/data_migration_plan/data_migration_plan.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.modules import get_module_path, scrub_dt_dn from frappe.modules.export_file import export_to_files, create_init_py diff --git a/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py index 3a33039c3d..14c585a82d 100644 --- a/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py +++ b/frappe/data_migration/doctype/data_migration_plan/test_data_migration_plan.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals import unittest class TestDataMigrationPlan(unittest.TestCase): diff --git a/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py index 85f879069c..ba4cf28eb8 100644 --- a/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py +++ b/frappe/data_migration/doctype/data_migration_plan_mapping/data_migration_plan_mapping.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class DataMigrationPlanMapping(Document): diff --git a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py index aed9c6cb1d..c35af5827b 100644 --- a/frappe/data_migration/doctype/data_migration_run/data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/data_migration_run.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json, math from frappe.model.document import Document from frappe import _ diff --git a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py index c6c3ea138c..ef7b70dca2 100644 --- a/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py +++ b/frappe/data_migration/doctype/data_migration_run/test_data_migration_run.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals import frappe, unittest class TestDataMigrationRun(unittest.TestCase): diff --git a/frappe/database/__init__.py b/frappe/database/__init__.py index 1f0d3f9bf5..a899bec3d1 100644 --- a/frappe/database/__init__.py +++ b/frappe/database/__init__.py @@ -4,8 +4,6 @@ # Database Module # -------------------- -from __future__ import unicode_literals - def setup_database(force, source_sql=None, verbose=None, no_mariadb_socket=False): import frappe if frappe.conf.db_type == 'postgres': diff --git a/frappe/database/database.py b/frappe/database/database.py index c9c1ec3909..81e24cc7ad 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -4,8 +4,6 @@ # Database Module # -------------------- -from __future__ import unicode_literals - import re import time import frappe @@ -19,13 +17,6 @@ from frappe.utils import now, getdate, cast_fieldtype, get_datetime from frappe.model.utils.link_count import flush_local_link_count from frappe.utils import cint -# imports - compatibility imports -from six import ( - integer_types, - string_types, - text_type, - iteritems -) class Database(object): """ @@ -277,7 +268,7 @@ class Database(object): for r in result: values = [] for value in r: - if as_utf8 and isinstance(value, text_type): + if as_utf8 and isinstance(value, str): value = value.encode('utf-8') values.append(value) @@ -294,7 +285,7 @@ class Database(object): """Returns true if the first row in the result has a Date, Datetime, Long Int.""" if result and result[0]: for v in result[0]: - if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, integer_types)): + if isinstance(v, (datetime.date, datetime.timedelta, datetime.datetime, int)): return True if formatted and isinstance(v, (int, float)): return True @@ -312,7 +303,7 @@ class Database(object): for r in res: nr = [] for val in r: - if as_utf8 and isinstance(val, text_type): + if as_utf8 and isinstance(val, str): val = val.encode('utf-8') nr.append(val) nres.append(nr) @@ -344,7 +335,7 @@ class Database(object): values[key] = value[1] if isinstance(value[1], (tuple, list)): # value is a list in tuple ("in", ("A", "B")) - _rhs = " ({0})".format(", ".join([self.escape(v) for v in value[1]])) + _rhs = " ({0})".format(", ".join(self.escape(v) for v in value[1])) del values[key] if _operator not in ["=", "!=", ">", ">=", "<", "<=", "like", "in", "not in", "not like"]: @@ -363,7 +354,7 @@ class Database(object): # docname is a number, convert to string filters = str(filters) - if isinstance(filters, string_types): + if isinstance(filters, str): filters = { "name": filters } for f in filters: @@ -428,7 +419,7 @@ class Database(object): user = frappe.db.get_values("User", "test@example.com", "*")[0] """ out = None - if cache and isinstance(filters, string_types) and \ + if cache and isinstance(filters, str) and \ (doctype, filters, fieldname) in self.value_cache: return self.value_cache[(doctype, filters, fieldname)] @@ -440,7 +431,7 @@ class Database(object): else: fields = fieldname if fieldname!="*": - if isinstance(fieldname, string_types): + if isinstance(fieldname, str): fields = [fieldname] else: fields = fieldname @@ -461,7 +452,7 @@ class Database(object): else: out = self.get_values_from_single(fields, filters, doctype, as_dict, debug, update) - if cache and isinstance(filters, string_types): + if cache and isinstance(filters, str): self.value_cache[(doctype, filters, fieldname)] = out return out @@ -673,7 +664,7 @@ class Database(object): where field in ({0}) and doctype=%s'''.format(', '.join(['%s']*len(keys))), list(keys) + [dt], debug=debug) - for key, value in iteritems(to_update): + for key, value in to_update.items(): self.sql('''insert into `tabSingles` (doctype, field, value) values (%s, %s, %s)''', (dt, key, value), debug=debug) @@ -811,7 +802,7 @@ class Database(object): :param dt: DocType name. :param dn: Document name or filter dict.""" - if isinstance(dt, string_types): + if isinstance(dt, str): if dt!="DocType" and dt==dn: return True # single always exists (!) try: @@ -1019,7 +1010,7 @@ class Database(object): :params values: list of list of values """ insert_list = [] - fields = ", ".join(["`"+field+"`" for field in fields]) + fields = ", ".join("`"+field+"`" for field in fields) for idx, value in enumerate(values): insert_list.append(tuple(value)) diff --git a/frappe/database/mariadb/schema.py b/frappe/database/mariadb/schema.py index 4bbecd2a2e..b40af59286 100644 --- a/frappe/database/mariadb/schema.py +++ b/frappe/database/mariadb/schema.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe from frappe import _ from frappe.database.schema import DBTable diff --git a/frappe/database/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py index 9b73d77171..6be08c66bb 100644 --- a/frappe/database/mariadb/setup_db.py +++ b/frappe/database/mariadb/setup_db.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe import os from frappe.database.db_manager import DbManager diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 6ac2767a71..8235277e30 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -2,7 +2,6 @@ import re import frappe import psycopg2 import psycopg2.extensions -from six import string_types from frappe.utils import cstr from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT @@ -253,7 +252,7 @@ class PostgresDatabase(Database): self.sql("""CREATE INDEX IF NOT EXISTS "{}" ON `{}`("{}")""".format(index_name, table_name, '", "'.join(fields))) def add_unique(self, doctype, fields, constraint_name=None): - if isinstance(fields, string_types): + if isinstance(fields, str): fields = [fields] if not constraint_name: constraint_name = "unique_" + "_".join(fields) diff --git a/frappe/database/postgres/setup_db.py b/frappe/database/postgres/setup_db.py index 3ee6b6a286..19ba681237 100644 --- a/frappe/database/postgres/setup_db.py +++ b/frappe/database/postgres/setup_db.py @@ -83,7 +83,6 @@ def get_root_connection(root_login=None, root_password=None): root_login = frappe.conf.get("root_login") or None if not root_login: - from six.moves import input root_login = input("Enter postgres super user: ") if not root_password: diff --git a/frappe/database/schema.py b/frappe/database/schema.py index 5f5ba06d8b..31f11dbd5e 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import re import frappe diff --git a/frappe/defaults.py b/frappe/defaults.py index 4bec6677c7..fde48d71ff 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.desk.notifications import clear_notifications from frappe.cache_manager import clear_defaults_cache, common_default_keys diff --git a/frappe/desk/__init__.py b/frappe/desk/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/desk/__init__.py +++ b/frappe/desk/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/desk/calendar.py b/frappe/desk/calendar.py index 064d870092..273b2654bf 100644 --- a/frappe/desk/calendar.py +++ b/frappe/desk/calendar.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe from frappe import _ import json @@ -27,7 +25,6 @@ def get_event_conditions(doctype, filters=None): @frappe.whitelist() def get_events(doctype, start, end, field_map, filters=None, fields=None): - field_map = frappe._dict(json.loads(field_map)) fields = frappe.parse_json(fields) @@ -38,8 +35,7 @@ def get_events(doctype, start, end, field_map, filters=None, fields=None): "color": d.fieldname }) - if filters: - filters = json.loads(filters or '') + filters = json.loads(filters) if filters else [] if not fields: fields = [field_map.start, field_map.end, field_map.title, 'name'] @@ -54,5 +50,5 @@ def get_events(doctype, start, end, field_map, filters=None, fields=None): [doctype, start_date, '<=', end], [doctype, end_date, '>=', start], ] - + fields = list({field for field in fields if field}) return frappe.get_list(doctype, fields=fields, filters=filters) diff --git a/frappe/desk/desk_page.py b/frappe/desk/desk_page.py index 6c5fdc6821..d373dbda0e 100644 --- a/frappe/desk/desk_page.py +++ b/frappe/desk/desk_page.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.translate import send_translations diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 1a3b1ca99b..cdab7d6d1b 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -2,12 +2,10 @@ # MIT License. See license.txt # Author - Shivam Mishra -from __future__ import unicode_literals import frappe from json import loads, dumps from frappe import _, DoesNotExistError, ValidationError, _dict from frappe.boot import get_allowed_pages, get_allowed_reports -from six import string_types from functools import wraps from frappe.cache_manager import ( build_domain_restriced_doctype_cache, @@ -61,7 +59,7 @@ class Workspace: shortcuts = self.doc.shortcuts + self.extended_shortcuts for section in cards: - links = loads(section.get('links')) if isinstance(section.get('links'), string_types) else section.get('links') + links = loads(section.get('links')) if isinstance(section.get('links'), str) else section.get('links') for item in links: if self.is_item_allowed(item.get('link_to'), item.get('link_type')): return True @@ -370,6 +368,7 @@ def get_desktop_page(page): 'allow_customization': not wspace.doc.disable_user_customization } except DoesNotExistError: + frappe.log_error(frappe.get_traceback()) return {} @frappe.whitelist() diff --git a/frappe/desk/doctype/bulk_update/bulk_update.py b/frappe/desk/doctype/bulk_update/bulk_update.py index 9b9f7d7a73..469ee839f1 100644 --- a/frappe/desk/doctype/bulk_update/bulk_update.py +++ b/frappe/desk/doctype/bulk_update/bulk_update.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ diff --git a/frappe/desk/doctype/calendar_view/calendar_view.py b/frappe/desk/doctype/calendar_view/calendar_view.py index ae8ab1eb46..3a986f3273 100644 --- a/frappe/desk/doctype/calendar_view/calendar_view.py +++ b/frappe/desk/doctype/calendar_view/calendar_view.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class CalendarView(Document): diff --git a/frappe/desk/doctype/console_log/console_log.py b/frappe/desk/doctype/console_log/console_log.py index 635c4c1ba7..5d0f1cfa93 100644 --- a/frappe/desk/doctype/console_log/console_log.py +++ b/frappe/desk/doctype/console_log/console_log.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/console_log/test_console_log.py b/frappe/desk/doctype/console_log/test_console_log.py index 04dc4f241f..3bb1605204 100644 --- a/frappe/desk/doctype/console_log/test_console_log.py +++ b/frappe/desk/doctype/console_log/test_console_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index 4e66318769..1d333609db 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document from frappe.modules.export_file import export_to_files from frappe.config import get_modules_from_all_apps_for_user @@ -22,7 +21,7 @@ class Dashboard(Document): def validate(self): if not frappe.conf.developer_mode and self.is_standard: - frappe.throw('Cannot edit Standard Dashboards') + frappe.throw(_("Cannot edit Standard Dashboards")) if self.is_standard: non_standard_docs_map = { diff --git a/frappe/desk/doctype/dashboard/test_dashboard.py b/frappe/desk/doctype/dashboard/test_dashboard.py index d5485d8f70..dd1bc31d86 100644 --- a/frappe/desk/doctype/dashboard/test_dashboard.py +++ b/frappe/desk/doctype/dashboard/test_dashboard.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestDashboard(unittest.TestCase): diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 48b34e6cd9..db5964e7b2 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -2,15 +2,13 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ import datetime import json from frappe.utils.dashboard import cache_source from frappe.utils import nowdate, getdate, get_datetime, cint, now_datetime -from frappe.utils.dateutils import\ - get_period, get_period_beginning, get_from_date_from_timespan, get_dates_from_timegrain +from frappe.utils.dateutils import get_period, get_period_beginning, get_from_date_from_timespan, get_dates_from_timegrain from frappe.model.naming import append_number_if_name_exists from frappe.boot import get_allowed_reports from frappe.config import get_modules_from_all_apps_for_user @@ -326,7 +324,7 @@ class DashboardChart(Document): def validate(self): if not frappe.conf.developer_mode and self.is_standard: - frappe.throw('Cannot edit Standard charts') + frappe.throw(_("Cannot edit Standard charts")) if self.chart_type != 'Custom' and self.chart_type != 'Report': self.check_required_field() self.check_document_type() diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index 72ab18385d..78d133b2d5 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest, frappe from frappe.utils import getdate, formatdate, get_last_day from frappe.utils.dateutils import get_period_ending, get_period diff --git a/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py b/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py index 734f27cc28..7d6f66daa2 100644 --- a/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py +++ b/frappe/desk/doctype/dashboard_chart_field/dashboard_chart_field.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py b/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py index 7cd4f9daa3..359801a303 100644 --- a/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py +++ b/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py b/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py index 6685009078..791dbc563b 100644 --- a/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py +++ b/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, os from frappe import _ from frappe.model.document import Document diff --git a/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py b/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py index 822526b591..53fe127dfb 100644 --- a/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py +++ b/frappe/desk/doctype/dashboard_chart_source/test_dashboard_chart_source.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestDashboardChartSource(unittest.TestCase): diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py index 4697d897fc..df61c52114 100644 --- a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py @@ -2,7 +2,6 @@ # 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 import frappe diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index fcf10ef61d..81a79cdb09 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -2,14 +2,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from frappe import _ import json import random from frappe.model.document import Document -from six import iteritems, string_types from frappe.utils.user import UserPermissions class DesktopIcon(Document): @@ -173,7 +170,7 @@ def add_user_icon(_doctype, _report=None, label=None, link=None, type='link', st @frappe.whitelist() def set_order(new_order, user=None): '''set new order by duplicating user icons (if user is set) or set global order''' - if isinstance(new_order, string_types): + if isinstance(new_order, str): new_order = json.loads(new_order) for i, module_name in enumerate(new_order): if module_name not in ('Explore',): @@ -232,7 +229,7 @@ def set_hidden_list(hidden_list, user=None): '''Sets property `hidden`=1 in **Desktop Icon** for given user. If user is None then it will set global values. It will also set the rest of the icons as shown (`hidden` = 0)''' - if isinstance(hidden_list, string_types): + if isinstance(hidden_list, str): hidden_list = json.loads(hidden_list) # set as hidden @@ -329,7 +326,7 @@ def sync_from_app(app): if isinstance(modules, dict): modules_list = [] - for m, desktop_icon in iteritems(modules): + for m, desktop_icon in modules.items(): desktop_icon['module_name'] = m modules_list.append(desktop_icon) else: diff --git a/frappe/desk/doctype/event/__init__.py b/frappe/desk/doctype/event/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/desk/doctype/event/__init__.py +++ b/frappe/desk/doctype/event/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/desk/doctype/event/event.py b/frappe/desk/doctype/event/event.py index 54905bed6a..57c89eaf2e 100644 --- a/frappe/desk/doctype/event/event.py +++ b/frappe/desk/doctype/event/event.py @@ -1,9 +1,7 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals -from six.moves import range -from six import string_types + import frappe import json @@ -106,7 +104,7 @@ class Event(Document): @frappe.whitelist() def delete_communication(event, reference_doctype, reference_docname): deleted_participant = frappe.get_doc(reference_doctype, reference_docname) - if isinstance(event, string_types): + if isinstance(event, str): event = json.loads(event) filters = [ @@ -168,7 +166,7 @@ def get_events(start, end, user=None, for_reminder=False, filters=None): if not user: user = frappe.session.user - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) filter_condition = get_filters_cond('Event', filters, []) diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index 2926a74a55..77211946a9 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - """Use blog post test to test user permissions logic""" import frappe diff --git a/frappe/desk/doctype/event_participants/event_participants.py b/frappe/desk/doctype/event_participants/event_participants.py index 18e4672140..ca4fae9930 100644 --- a/frappe/desk/doctype/event_participants/event_participants.py +++ b/frappe/desk/doctype/event_participants/event_participants.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class EventParticipants(Document): diff --git a/frappe/patches/v4_1/__init__.py b/frappe/desk/doctype/form_tour/__init__.py similarity index 100% rename from frappe/patches/v4_1/__init__.py rename to frappe/desk/doctype/form_tour/__init__.py diff --git a/frappe/desk/doctype/form_tour/form_tour.js b/frappe/desk/doctype/form_tour/form_tour.js new file mode 100644 index 0000000000..efb853cfa5 --- /dev/null +++ b/frappe/desk/doctype/form_tour/form_tour.js @@ -0,0 +1,123 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Form Tour', { + setup: function(frm) { + if (!frm.doc.is_standard || frappe.boot.developer_mode) { + frm.trigger('setup_queries'); + } + }, + + refresh(frm) { + if (frm.doc.is_standard && !frappe.boot.developer_mode) { + frm.trigger("disable_form"); + } + + frm.add_custom_button(__('Show Tour'), async () => { + const issingle = await check_if_single(frm.doc.reference_doctype); + + if (issingle) { + frappe.set_route('Form', frm.doc.reference_doctype); + } else { + const new_name = 'new-' + frappe.scrub(frm.doc.reference_doctype) + '-1'; + frappe.set_route('Form', frm.doc.reference_doctype, new_name); + } + frappe.utils.sleep(500).then(() => { + const tour_name = frm.doc.name; + cur_frm.tour + .init({ tour_name }) + .then(() => cur_frm.tour.start()); + }); + }); + }, + + disable_form: function(frm) { + frm.set_read_only(); + frm.fields + .filter((field) => field.has_input) + .forEach((field) => { + frm.set_df_property(field.df.fieldname, "read_only", "1"); + }); + frm.disable_save(); + }, + + setup_queries(frm) { + frm.set_query("reference_doctype", function() { + return { + filters: { + istable: 0 + } + }; + }); + + frm.set_query("field", "steps", function() { + return { + query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list", + filters: { + doctype: frm.doc.reference_doctype, + hidden: 0 + } + }; + }); + + frm.set_query("parent_field", "steps", function() { + return { + query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list", + filters: { + doctype: frm.doc.reference_doctype, + fieldtype: "Table", + hidden: 0, + } + }; + }); + + frm.trigger('reference_doctype'); + }, + + reference_doctype(frm) { + if (!frm.doc.reference_doctype) return; + + frappe.db.get_list('DocField', { + filters: { + parent: frm.doc.reference_doctype, + parenttype: 'DocType', + fieldtype: 'Table' + }, + fields: ['options'] + }).then(res => { + if (Array.isArray(res)) { + frm.child_doctypes = res.map(r => r.options); + } + }); + + } +}); + +frappe.ui.form.on('Form Tour Step', { + parent_field(frm, cdt, cdn) { + const child_row = locals[cdt][cdn]; + frappe.model.set_value(cdt, cdn, 'field', ''); + const field_control = get_child_field("steps", cdn, "field"); + field_control.get_query = function() { + return { + query: "frappe.desk.doctype.form_tour.form_tour.get_docfield_list", + filters: { + doctype: child_row.child_doctype, + hidden: 0 + } + }; + }; + } +}); + +function get_child_field(child_table, child_name, fieldname) { + // gets the field from grid row form + const grid = cur_frm.fields_dict[child_table].grid; + const grid_row = grid.grid_rows_by_docname[child_name]; + return grid_row.grid_form.fields_dict[fieldname]; +} + +async function check_if_single(doctype) { + const { message } = await frappe.db.get_value('DocType', doctype, 'issingle'); + return message.issingle || 0; +} \ No newline at end of file diff --git a/frappe/desk/doctype/form_tour/form_tour.json b/frappe/desk/doctype/form_tour/form_tour.json new file mode 100644 index 0000000000..e4ea528fcc --- /dev/null +++ b/frappe/desk/doctype/form_tour/form_tour.json @@ -0,0 +1,91 @@ +{ + "actions": [], + "autoname": "field:title", + "creation": "2021-05-21 23:02:52.242721", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "reference_doctype", + "module", + "is_standard", + "save_on_complete", + "section_break_3", + "steps" + ], + "fields": [ + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference Document", + "options": "DocType", + "reqd": 1 + }, + { + "depends_on": "reference_doctype", + "fieldname": "steps", + "fieldtype": "Table", + "label": "Steps", + "options": "Form Tour Step", + "reqd": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "reqd": 1, + "unique": 1 + }, + { + "default": "0", + "fieldname": "save_on_complete", + "fieldtype": "Check", + "label": "Save on Completion" + }, + { + "default": "0", + "fieldname": "is_standard", + "fieldtype": "Check", + "label": "Is Standard" + }, + { + "fetch_from": "reference_doctype.module", + "fieldname": "module", + "fieldtype": "Link", + "hidden": 1, + "label": "Module", + "options": "Module Def", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-06-06 20:32:54.068774", + "modified_by": "Administrator", + "module": "Desk", + "name": "Form Tour", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 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/desk/doctype/form_tour/form_tour.py b/frappe/desk/doctype/form_tour/form_tour.py new file mode 100644 index 0000000000..dbc667ce28 --- /dev/null +++ b/frappe/desk/doctype/form_tour/form_tour.py @@ -0,0 +1,62 @@ +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe.modules.export_file import export_to_files + +class FormTour(Document): + def before_insert(self): + if not self.is_standard: + return + + # while syncing, set proper docfield reference + for d in self.steps: + if not frappe.db.exists('DocField', d.field): + d.field = frappe.db.get_value('DocField', { + 'fieldname': d.fieldname, 'parent': self.reference_doctype, 'fieldtype': d.fieldtype + }, "name") + + if d.is_table_field and not frappe.db.exists('DocField', d.parent_field): + d.parent_field = frappe.db.get_value('DocField', { + 'fieldname': d.parent_fieldname, 'parent': self.reference_doctype, 'fieldtype': 'Table' + }, "name") + + def on_update(self): + if frappe.conf.developer_mode and self.is_standard: + export_to_files([['Form Tour', self.name]], self.module) + + def before_export(self, doc): + for d in doc.steps: + d.field = "" + d.parent_field = "" + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_docfield_list(doctype, txt, searchfield, start, page_len, filters): + or_filters = [ + ['fieldname', 'like', '%' + txt + '%'], + ['label', 'like', '%' + txt + '%'], + ['fieldtype', 'like', '%' + txt + '%'] + ] + + parent_doctype = filters.get('doctype') + fieldtype = filters.get('fieldtype') + if not fieldtype: + excluded_fieldtypes = ['Column Break'] + excluded_fieldtypes += filters.get('excluded_fieldtypes', []) + fieldtype_filter = ['not in', excluded_fieldtypes] + else: + fieldtype_filter = fieldtype + + docfields = frappe.get_all( + doctype, + fields=["name as value", "label", "fieldtype"], + filters={'parent': parent_doctype, 'fieldtype': fieldtype_filter}, + or_filters=or_filters, + limit_start=start, + limit_page_length=page_len, + order_by="idx", + as_list=1, + ) + return docfields diff --git a/frappe/desk/doctype/form_tour/test_form_tour.py b/frappe/desk/doctype/form_tour/test_form_tour.py new file mode 100644 index 0000000000..a4a796ce41 --- /dev/null +++ b/frappe/desk/doctype/form_tour/test_form_tour.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt + +# import frappe +import unittest + +class TestFormTour(unittest.TestCase): + pass diff --git a/frappe/patches/v4_2/__init__.py b/frappe/desk/doctype/form_tour_step/__init__.py similarity index 100% rename from frappe/patches/v4_2/__init__.py rename to frappe/desk/doctype/form_tour_step/__init__.py diff --git a/frappe/desk/doctype/form_tour_step/form_tour_step.json b/frappe/desk/doctype/form_tour_step/form_tour_step.json new file mode 100644 index 0000000000..3b6c91a208 --- /dev/null +++ b/frappe/desk/doctype/form_tour_step/form_tour_step.json @@ -0,0 +1,151 @@ +{ + "actions": [], + "creation": "2021-05-21 23:05:45.342114", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "is_table_field", + "section_break_2", + "parent_field", + "field", + "title", + "description", + "column_break_2", + "position", + "label", + "has_next_condition", + "next_step_condition", + "section_break_13", + "fieldname", + "parent_fieldname", + "fieldtype", + "child_doctype" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "columns": 4, + "fieldname": "description", + "fieldtype": "HTML Editor", + "in_list_view": 1, + "label": "Description", + "reqd": 1 + }, + { + "depends_on": "eval: (!doc.is_table_field || (doc.is_table_field && doc.parent_field))", + "fieldname": "field", + "fieldtype": "Link", + "label": "Field", + "options": "DocField", + "reqd": 1 + }, + { + "fetch_from": "field.fieldname", + "fieldname": "fieldname", + "fieldtype": "Data", + "hidden": 1, + "label": "Fieldname", + "read_only": 1 + }, + { + "fetch_from": "field.label", + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "Bottom", + "fieldname": "position", + "fieldtype": "Select", + "label": "Position", + "options": "Left\nLeft Center\nLeft Bottom\nTop\nTop Center\nTop Right\nRight\nRight Center\nRight Bottom\nBottom\nBottom Center\nBottom Right\nMid Center" + }, + { + "depends_on": "has_next_condition", + "fieldname": "next_step_condition", + "fieldtype": "Code", + "label": "Next Step Condition", + "oldfieldname": "condition", + "options": "JS" + }, + { + "default": "0", + "fieldname": "has_next_condition", + "fieldtype": "Check", + "label": "Has Next Condition" + }, + { + "default": "0", + "fetch_from": "field.fieldtype", + "fieldname": "fieldtype", + "fieldtype": "Data", + "hidden": 1, + "label": "Fieldtype", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_table_field", + "fieldtype": "Check", + "label": "Is Table Field" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "depends_on": "is_table_field", + "fieldname": "parent_field", + "fieldtype": "Link", + "label": "Parent Field", + "mandatory_depends_on": "is_table_field", + "options": "DocField" + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Hidden Fields" + }, + { + "fetch_from": "parent_field.options", + "fieldname": "child_doctype", + "fieldtype": "Data", + "hidden": 1, + "label": "Child Doctype", + "read_only": 1 + }, + { + "fetch_from": "parent_field.fieldname", + "fieldname": "parent_fieldname", + "fieldtype": "Data", + "hidden": 1, + "label": "Parent Fieldname", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-06-06 20:52:21.076972", + "modified_by": "Administrator", + "module": "Desk", + "name": "Form Tour Step", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/form_tour_step/form_tour_step.py b/frappe/desk/doctype/form_tour_step/form_tour_step.py new file mode 100644 index 0000000000..0df5665c63 --- /dev/null +++ b/frappe/desk/doctype/form_tour_step/form_tour_step.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class FormTourStep(Document): + pass diff --git a/frappe/desk/doctype/global_search_doctype/global_search_doctype.py b/frappe/desk/doctype/global_search_doctype/global_search_doctype.py index 4c9a948278..de8a48af01 100644 --- a/frappe/desk/doctype/global_search_doctype/global_search_doctype.py +++ b/frappe/desk/doctype/global_search_doctype/global_search_doctype.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.py b/frappe/desk/doctype/global_search_settings/global_search_settings.py index 85c9687ab3..9112349c1b 100644 --- a/frappe/desk/doctype/global_search_settings/global_search_settings.py +++ b/frappe/desk/doctype/global_search_settings/global_search_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ @@ -22,7 +21,7 @@ class GlobalSearchSettings(Document): dts.append(dt.document_type) if core_dts: - core_dts = (", ".join([frappe.bold(dt) for dt in core_dts])) + core_dts = ", ".join(frappe.bold(dt) for dt in core_dts) frappe.throw(_("Core Modules {0} cannot be searched in Global Search.").format(core_dts)) if repeated_dts: @@ -61,7 +60,7 @@ def update_global_search_doctypes(): if search_doctypes.get(domain): global_search_doctypes.extend(search_doctypes.get(domain)) - doctype_list = set([dt.name for dt in frappe.get_all("DocType")]) + doctype_list = {dt.name for dt in frappe.get_all("DocType")} allowed_in_global_search = [] for dt in global_search_doctypes: diff --git a/frappe/desk/doctype/kanban_board/kanban_board.py b/frappe/desk/doctype/kanban_board/kanban_board.py index a655e9e1da..5100727f43 100644 --- a/frappe/desk/doctype/kanban_board/kanban_board.py +++ b/frappe/desk/doctype/kanban_board/kanban_board.py @@ -2,12 +2,10 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import json from frappe import _ from frappe.model.document import Document -from six import iteritems class KanbanBoard(Document): @@ -107,7 +105,7 @@ def update_order(board_name, order): order_dict = json.loads(order) updated_cards = [] - for col_name, cards in iteritems(order_dict): + for col_name, cards in order_dict.items(): order_list = [] for card in cards: column = frappe.get_value( diff --git a/frappe/desk/doctype/kanban_board/test_kanban_board.py b/frappe/desk/doctype/kanban_board/test_kanban_board.py index 33947f4a54..f9503d736a 100644 --- a/frappe/desk/doctype/kanban_board/test_kanban_board.py +++ b/frappe/desk/doctype/kanban_board/test_kanban_board.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/desk/doctype/kanban_board_column/kanban_board_column.py b/frappe/desk/doctype/kanban_board_column/kanban_board_column.py index 4ea30d21b2..aebba3351c 100644 --- a/frappe/desk/doctype/kanban_board_column/kanban_board_column.py +++ b/frappe/desk/doctype/kanban_board_column/kanban_board_column.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/list_filter/list_filter.py b/frappe/desk/doctype/list_filter/list_filter.py index 035f7e90b9..2467ae40a4 100644 --- a/frappe/desk/doctype/list_filter/list_filter.py +++ b/frappe/desk/doctype/list_filter/list_filter.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json from frappe.model.document import Document diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index 74e029f499..f4a288b7ba 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/list_view_settings/test_list_view_settings.py b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py index c1b2f4a0da..00010d7604 100644 --- a/frappe/desk/doctype/list_view_settings/test_list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py index 8315c0b304..6f01e0fd8d 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -2,7 +2,6 @@ # 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 from frappe.modules.export_file import export_to_files diff --git a/frappe/desk/doctype/module_onboarding/test_module_onboarding.py b/frappe/desk/doctype/module_onboarding/test_module_onboarding.py index ef305667b1..39184401a1 100644 --- a/frappe/desk/doctype/module_onboarding/test_module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/test_module_onboarding.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/note/note.py b/frappe/desk/doctype/note/note.py index c54689418e..790f9a514c 100644 --- a/frappe/desk/doctype/note/note.py +++ b/frappe/desk/doctype/note/note.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/note/test_note.py b/frappe/desk/doctype/note/test_note.py index 38894a9c3d..1bb1730357 100644 --- a/frappe/desk/doctype/note/test_note.py +++ b/frappe/desk/doctype/note/test_note.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -from __future__ import unicode_literals import frappe import unittest diff --git a/frappe/desk/doctype/note_seen_by/note_seen_by.py b/frappe/desk/doctype/note_seen_by/note_seen_by.py index 6123f20929..cec4628b20 100644 --- a/frappe/desk/doctype/note_seen_by/note_seen_by.py +++ b/frappe/desk/doctype/note_seen_by/note_seen_by.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 25af92f532..414f272f59 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document diff --git a/frappe/desk/doctype/notification_log/test_notification_log.py b/frappe/desk/doctype/notification_log/test_notification_log.py index e59aee30c9..af4dee8df3 100644 --- a/frappe/desk/doctype/notification_log/test_notification_log.py +++ b/frappe/desk/doctype/notification_log/test_notification_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe from frappe.desk.form.assign_to import add as assign_task import unittest diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 4ab40bffe9..eb3a16435f 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py b/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py index f005efae76..6931e77754 100644 --- a/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py +++ b/frappe/desk/doctype/notification_subscribed_document/notification_subscribed_document.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 7d1a697f6b..d8d5fe0953 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -2,7 +2,6 @@ # 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 from frappe.utils import cint diff --git a/frappe/desk/doctype/number_card/test_number_card.py b/frappe/desk/doctype/number_card/test_number_card.py index 4aa1ecf282..c395f5f915 100644 --- a/frappe/desk/doctype/number_card/test_number_card.py +++ b/frappe/desk/doctype/number_card/test_number_card.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/number_card_link/number_card_link.py b/frappe/desk/doctype/number_card_link/number_card_link.py index 67ad7e70cd..6c16f45f4b 100644 --- a/frappe/desk/doctype/number_card_link/number_card_link.py +++ b/frappe/desk/doctype/number_card_link/number_card_link.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/onboarding_permission/onboarding_permission.py b/frappe/desk/doctype/onboarding_permission/onboarding_permission.py index f8772480df..40d3dc33b1 100644 --- a/frappe/desk/doctype/onboarding_permission/onboarding_permission.py +++ b/frappe/desk/doctype/onboarding_permission/onboarding_permission.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py b/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py index 9a7e8ae6fd..80b166de0a 100644 --- a/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py +++ b/frappe/desk/doctype/onboarding_permission/test_onboarding_permission.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.py b/frappe/desk/doctype/onboarding_step/onboarding_step.py index e1cc5dfba4..10bd8926ce 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.py +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/onboarding_step/test_onboarding_step.py b/frappe/desk/doctype/onboarding_step/test_onboarding_step.py index 66bd0c6660..2425577478 100644 --- a/frappe/desk/doctype/onboarding_step/test_onboarding_step.py +++ b/frappe/desk/doctype/onboarding_step/test_onboarding_step.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py b/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py index ea34de6088..c79244c4ad 100644 --- a/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py +++ b/frappe/desk/doctype/onboarding_step_map/onboarding_step_map.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/desk/doctype/route_history/route_history.py b/frappe/desk/doctype/route_history/route_history.py index 12d898afa5..b82077f485 100644 --- a/frappe/desk/doctype/route_history/route_history.py +++ b/frappe/desk/doctype/route_history/route_history.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 6c87ca8c36..e2b5656bc0 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -2,8 +2,6 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import json import frappe diff --git a/frappe/desk/doctype/system_console/test_system_console.py b/frappe/desk/doctype/system_console/test_system_console.py index 55ef199122..743c2d6dde 100644 --- a/frappe/desk/doctype/system_console/test_system_console.py +++ b/frappe/desk/doctype/system_console/test_system_console.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index 7e016ee91b..4ea5c9cd7e 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils import unique @@ -132,7 +131,7 @@ def update_tags(doc, tags): :param doc: Document to be added to global tags """ - new_tags = list(set([tag.strip() for tag in tags.split(",") if tag])) + new_tags = {tag.strip() for tag in tags.split(",") if tag} for tag in new_tags: if not frappe.db.exists("Tag Link", {"parenttype": doc.doctype, "parent": doc.name, "tag": tag}): @@ -187,4 +186,4 @@ def get_documents_for_tag(tag): @frappe.whitelist() def get_tags_list_for_awesomebar(): - return [t.name for t in frappe.get_list("Tag")] \ No newline at end of file + return [t.name for t in frappe.get_list("Tag")] diff --git a/frappe/desk/doctype/tag/test_tag.py b/frappe/desk/doctype/tag/test_tag.py index 8efd692f43..442a891fd8 100644 --- a/frappe/desk/doctype/tag/test_tag.py +++ b/frappe/desk/doctype/tag/test_tag.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/tag_link/tag_link.py b/frappe/desk/doctype/tag_link/tag_link.py index 87c8af7212..4c5149f42c 100644 --- a/frappe/desk/doctype/tag_link/tag_link.py +++ b/frappe/desk/doctype/tag_link/tag_link.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/tag_link/test_tag_link.py b/frappe/desk/doctype/tag_link/test_tag_link.py index 1c22ac18bc..297ee3cc96 100644 --- a/frappe/desk/doctype/tag_link/test_tag_link.py +++ b/frappe/desk/doctype/tag_link/test_tag_link.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/todo/__init__.py b/frappe/desk/doctype/todo/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/desk/doctype/todo/__init__.py +++ b/frappe/desk/doctype/todo/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/desk/doctype/todo/test_todo.py b/frappe/desk/doctype/todo/test_todo.py index de5b6724a6..b38e4a059a 100644 --- a/frappe/desk/doctype/todo/test_todo.py +++ b/frappe/desk/doctype/todo/test_todo.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.model.db_query import DatabaseQuery diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index a766375fde..4696563445 100644 --- a/frappe/desk/doctype/todo/todo.py +++ b/frappe/desk/doctype/todo/todo.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import json @@ -93,7 +92,7 @@ def get_permission_query_conditions(user): if not user: user = frappe.session.user todo_roles = frappe.permissions.get_doctype_roles('ToDo') - if 'All' in todo_roles: + if 'All' in todo_roles: todo_roles.remove('All') if any(check in todo_roles for check in frappe.get_roles(user)): @@ -105,7 +104,7 @@ def get_permission_query_conditions(user): def has_permission(doc, ptype="read", user=None): user = user or frappe.session.user todo_roles = frappe.permissions.get_doctype_roles('ToDo', ptype) - if 'All' in todo_roles: + if 'All' in todo_roles: todo_roles.remove('All') if any(check in todo_roles for check in frappe.get_roles(user)): diff --git a/frappe/desk/doctype/workspace/test_workspace.py b/frappe/desk/doctype/workspace/test_workspace.py index 7a3f122ee2..619b3608eb 100644 --- a/frappe/desk/doctype/workspace/test_workspace.py +++ b/frappe/desk/doctype/workspace/test_workspace.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 0934138821..41b0227f2a 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -2,7 +2,6 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.modules.export_file import export_to_files @@ -44,20 +43,19 @@ class Workspace(Document): def get_link_groups(self): cards = [] - current_card = { + current_card = frappe._dict({ "label": "Link", "type": "Card Break", "icon": None, "hidden": False, - } + }) card_links = [] for link in self.links: link = link.as_dict() if link.type == "Card Break": - - if card_links: + if card_links and (not current_card.only_for or current_card.only_for == frappe.get_system_settings('country')): current_card['links'] = card_links cards.append(current_card) diff --git a/frappe/desk/doctype/workspace_chart/workspace_chart.py b/frappe/desk/doctype/workspace_chart/workspace_chart.py index 0bb6194d2e..6ec7abfd3c 100644 --- a/frappe/desk/doctype/workspace_chart/workspace_chart.py +++ b/frappe/desk/doctype/workspace_chart/workspace_chart.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/workspace_link/workspace_link.py b/frappe/desk/doctype/workspace_link/workspace_link.py index 8a139077a6..d6ccc5306a 100644 --- a/frappe/desk/doctype/workspace_link/workspace_link.py +++ b/frappe/desk/doctype/workspace_link/workspace_link.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py index d676f08b73..83b446e454 100644 --- a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py +++ b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py @@ -2,7 +2,6 @@ # Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/desk/form/__init__.py b/frappe/desk/form/__init__.py index 4dbcd0d163..0e57cb68c3 100644 --- a/frappe/desk/form/__init__.py +++ b/frappe/desk/form/__init__.py @@ -1,4 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index aee7a8e52a..3eda291d1e 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals """assign/unassign to ToDo""" import frappe diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py index f5e5c0ca9b..7f65f76a58 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import frappe.utils from frappe.utils import get_url_to_form diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index a62e2837d5..ae48b7fc6b 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -1,9 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import json from collections import defaultdict -from six import string_types + import frappe import frappe.desk.form.load import frappe.desk.form.meta @@ -11,6 +10,7 @@ from frappe import _ from frappe.model.meta import is_single from frappe.modules import load_doctype_module + @frappe.whitelist() def get_submitted_linked_docs(doctype, name, docs=None, visited=None): """ @@ -87,7 +87,7 @@ def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]): """ docs = json.loads(docs) - if isinstance(ignore_doctypes_on_cancel_all, string_types): + if isinstance(ignore_doctypes_on_cancel_all, str): ignore_doctypes_on_cancel_all = json.loads(ignore_doctypes_on_cancel_all) for i, doc in enumerate(docs, 1): if validate_linked_doc(doc, ignore_doctypes_on_cancel_all): @@ -139,7 +139,7 @@ def get_exempted_doctypes(): @frappe.whitelist() def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): - if isinstance(linkinfo, string_types): + if isinstance(linkinfo, str): # additional fields are added in linkinfo linkinfo = json.loads(linkinfo) @@ -202,7 +202,8 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None): else: link_fieldnames = link.get("fieldname") if link_fieldnames: - if isinstance(link_fieldnames, string_types): link_fieldnames = [link_fieldnames] + if isinstance(link_fieldnames, str): + link_fieldnames = [link_fieldnames] or_filters = [[dt, fieldname, '=', name] for fieldname in link_fieldnames] # dynamic link if link.get("doctype_fieldname"): diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index d81bb8c26c..a62bfd01d0 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, json import frappe.utils import frappe.share @@ -11,7 +10,7 @@ from frappe.model.utils.user_settings import get_user_settings from frappe.permissions import get_doc_permissions from frappe.desk.form.document_follow import is_document_followed from frappe import _ -from six.moves.urllib.parse import quote +from urllib.parse import quote @frappe.whitelist(allow_guest=True) def getdoc(doctype, name, user=None): diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index 087cc54d9d..cf3606e785 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -1,20 +1,16 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -# metadata - -from __future__ import unicode_literals -import frappe, os -from frappe.model.meta import Meta -from frappe.modules import scrub, get_module_path, load_doctype_module -from frappe.utils import get_html_format -from frappe.translate import make_dict_from_messages, extract_messages_from_code -from frappe.model.utils import render_include -from frappe.build import scrub_html_template - import io +import os + +import frappe +from frappe.build import scrub_html_template +from frappe.model.meta import Meta +from frappe.model.utils import render_include +from frappe.modules import get_module_path, load_doctype_module, scrub +from frappe.translate import extract_messages_from_code, make_dict_from_messages +from frappe.utils import get_html_format -from six import iteritems def get_meta(doctype, cached=True): # don't cache for developer mode as js files, templates may be edited @@ -199,7 +195,7 @@ class FormMeta(Meta): app = module.__name__.split(".")[0] templates = {} if hasattr(module, "form_grid_templates"): - for key, path in iteritems(module.form_grid_templates): + for key, path in module.form_grid_templates.items(): templates[key] = get_html_format(frappe.get_app_path(app, path)) self.set("__form_grid_templates", templates) diff --git a/frappe/desk/form/save.py b/frappe/desk/form/save.py index da43b14fce..a7a4b829d8 100644 --- a/frappe/desk/form/save.py +++ b/frappe/desk/form/save.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, json from frappe.desk.form.load import run_onload diff --git a/frappe/desk/form/test_form.py b/frappe/desk/form/test_form.py index ff0343b6e0..f3c4132777 100644 --- a/frappe/desk/form/test_form.py +++ b/frappe/desk/form/test_form.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, unittest from frappe.desk.form.linked_with import get_linked_docs, get_linked_doctypes diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index 395d2b9571..bfceee6ea2 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, json import frappe.desk.form.meta import frappe.desk.form.load @@ -9,7 +8,6 @@ from frappe.desk.form.document_follow import follow_document from frappe.utils.file_manager import extract_images_from_html from frappe import _ -from six import string_types @frappe.whitelist() def remove_attach(): @@ -90,7 +88,7 @@ def get_next(doctype, value, prev, filters=None, sort_order='desc', sort_field=' prev = int(prev) if not filters: filters = [] - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) # # condition based on sort order diff --git a/frappe/desk/gantt.py b/frappe/desk/gantt.py index 521884beaa..7f0889c751 100644 --- a/frappe/desk/gantt.py +++ b/frappe/desk/gantt.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, json @frappe.whitelist() diff --git a/frappe/desk/leaderboard.py b/frappe/desk/leaderboard.py index d651687256..a98ae1a1c6 100644 --- a/frappe/desk/leaderboard.py +++ b/frappe/desk/leaderboard.py @@ -1,5 +1,3 @@ - -from __future__ import unicode_literals, print_function import frappe from frappe.utils import get_fullname diff --git a/frappe/desk/like.py b/frappe/desk/like.py index 6d2e9704af..d44d58a761 100644 --- a/frappe/desk/like.py +++ b/frappe/desk/like.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - """Allow adding of likes to documents""" import frappe, json diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 91dc0f3ba9..d2c84d36bf 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -1,7 +1,5 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe @frappe.whitelist(allow_guest=True) diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index df25b77e2d..021698ac92 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import json from frappe import _ diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 4b584a2429..c84027928e 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -1,11 +1,8 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe from frappe.desk.doctype.notification_settings.notification_settings import get_subscribed_documents -from six import string_types import json @frappe.whitelist() @@ -149,7 +146,7 @@ def clear_doctype_notifications(doc, method=None, *args, **kwargs): config = get_notification_config() if not config: return - if isinstance(doc, string_types): + if isinstance(doc, str): doctype = doc # assuming doctype name was passed directly else: doctype = doc.doctype @@ -213,7 +210,7 @@ def get_filters_for(doctype): '''get open filters for doctype''' config = get_notification_config() doctype_config = config.get("for_doctype").get(doctype, {}) - filters = doctype_config if not isinstance(doctype_config, string_types) else None + filters = doctype_config if not isinstance(doctype_config, str) else None return filters diff --git a/frappe/desk/page/activity/__init__.py b/frappe/desk/page/activity/__init__.py index baffc48825..8b13789179 100644 --- a/frappe/desk/page/activity/__init__.py +++ b/frappe/desk/page/activity/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals + diff --git a/frappe/desk/page/activity/activity.py b/frappe/desk/page/activity/activity.py index 7de294d2f0..3abc8e0ea5 100644 --- a/frappe/desk/page/activity/activity.py +++ b/frappe/desk/page/activity/activity.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals import frappe from frappe.utils import cint from frappe.core.doctype.activity_log.feed import get_feed_match_conditions diff --git a/frappe/desk/page/backups/backups.py b/frappe/desk/page/backups/backups.py index eaa0c65143..2229a6d89e 100644 --- a/frappe/desk/page/backups/backups.py +++ b/frappe/desk/page/backups/backups.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import os import frappe from frappe import _ diff --git a/frappe/desk/page/leaderboard/leaderboard.py b/frappe/desk/page/leaderboard/leaderboard.py index 819e7fe9d1..9469096f50 100644 --- a/frappe/desk/page/leaderboard/leaderboard.py +++ b/frappe/desk/page/leaderboard/leaderboard.py @@ -1,7 +1,5 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function import frappe @frappe.whitelist() diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index 6d3aaee22b..06301cdeaf 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -1,8 +1,6 @@ # 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.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 1ac5279508..5edb44e182 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals - import frappe, json, os from frappe.utils import strip, cint from frappe.translate import (set_default_language, get_dict, send_translations) @@ -10,7 +8,6 @@ from frappe.geo.country_info import get_country_info from frappe.utils.password import update_password from werkzeug.useragents import UserAgent from . import install_fixtures -from six import string_types def get_setup_stages(args): @@ -208,14 +205,14 @@ def update_user_name(args): def parse_args(args): if not args: args = frappe.local.form_dict - if isinstance(args, string_types): + if isinstance(args, str): args = json.loads(args) args = frappe._dict(args) # strip the whitespace for key, value in args.items(): - if isinstance(value, string_types): + if isinstance(value, str): args[key] = strip(value) return args @@ -294,7 +291,7 @@ def reset_is_first_startup(): def prettify_args(args): # remove attachments for key, val in args.items(): - if isinstance(val, string_types) and "data:image" in val: + if isinstance(val, str) and "data:image" in val: filename = val.split("data:image", 1)[0].strip(", ") size = round((len(val) * 3 / 4) / 1048576.0, 2) args[key] = "Image Attached: '{0}' of size {1} MB".format(filename, size) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index befaf7b01f..3c0ebf11c1 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe import os import json @@ -22,7 +20,6 @@ from frappe.model.utils import render_include from frappe.translate import send_translations import frappe.desk.reportview from frappe.permissions import get_role_permissions -from six import string_types, iteritems from datetime import timedelta from frappe.core.utils import ljust_list @@ -66,7 +63,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) user = user or frappe.session.user filters = filters or [] - if filters and isinstance(filters, string_types): + if filters and isinstance(filters, str): filters = json.loads(filters) res = [] @@ -222,7 +219,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False, cust and not custom_columns ): if filters: - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) dn = filters.get("prepared_report_name") @@ -317,7 +314,7 @@ def export_query(): data.pop("cmd", None) data.pop("csrf_token", None) - if isinstance(data.get("filters"), string_types): + if isinstance(data.get("filters"), str): filters = json.loads(data["filters"]) if data.get("report_name"): @@ -332,7 +329,7 @@ def export_query(): include_indentation = data.get("include_indentation") visible_idx = data.get("visible_idx") - if isinstance(visible_idx, string_types): + if isinstance(visible_idx, str): visible_idx = json.loads(visible_idx) if file_format_type == "Excel": @@ -363,7 +360,7 @@ def export_query(): def handle_duration_fieldtype_values(result, columns): for i, col in enumerate(columns): fieldtype = None - if isinstance(col, string_types): + if isinstance(col, str): col = col.split(":") if len(col) > 1: if col[1]: @@ -433,7 +430,7 @@ def add_total_row(result, columns, meta=None): has_percent = [] for i, col in enumerate(columns): fieldtype, options, fieldname = None, None, None - if isinstance(col, string_types): + if isinstance(col, str): if meta: # get fieldtype from the meta field = meta.get_field(col) @@ -483,7 +480,7 @@ def add_total_row(result, columns, meta=None): total_row[i] = flt(total_row[i]) / len(result) first_col_fieldtype = None - if isinstance(columns[0], string_types): + if isinstance(columns[0], str): first_col = columns[0].split(":") if len(first_col) > 1: first_col_fieldtype = first_col[1].split("/")[0] @@ -701,7 +698,7 @@ def get_linked_doctypes(columns, data): if val and col not in columns_with_value: columns_with_value.append(col) - items = list(iteritems(linked_doctypes)) + items = list(linked_doctypes.items()) for doctype, key in items: if key not in columns_with_value: @@ -728,7 +725,7 @@ def get_column_as_dict(col): col_dict = frappe._dict() # string - if isinstance(col, string_types): + if isinstance(col, str): col = col.split(":") if len(col) > 1: if "/" in col[1]: diff --git a/frappe/desk/report/todo/todo.py b/frappe/desk/report/todo/todo.py index f4fe2dc805..6bd22b843e 100644 --- a/frappe/desk/report/todo/todo.py +++ b/frappe/desk/report/todo/todo.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import getdate diff --git a/frappe/desk/report_dump.py b/frappe/desk/report_dump.py index 86b1765814..b2d3ca3443 100644 --- a/frappe/desk/report_dump.py +++ b/frappe/desk/report_dump.py @@ -1,8 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals -from six.moves import range + import frappe import json import copy diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 86f8ec0aa7..55515856f1 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -1,16 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals """build query for doclistview and return results""" import frappe, json -from six.moves import range import frappe.permissions from frappe.model.db_query import DatabaseQuery from frappe.model import default_fields, optional_fields from frappe import _ -from six import string_types, StringIO +from io import StringIO from frappe.core.doctype.access_log.access_log import make_access_log from frappe.utils import cstr, format_duration from frappe.model.base_document import get_controller @@ -171,7 +169,7 @@ def get_meta_and_docfield(fieldname, data): return meta, df def update_wildcard_field_param(data): - if ((isinstance(data.fields, string_types) and data.fields == "*") + if ((isinstance(data.fields, str) and data.fields == "*") or (isinstance(data.fields, (list, tuple)) and len(data.fields) == 1 and data.fields[0] == "*")): data.fields = frappe.db.get_table_columns(data.doctype) return True @@ -191,15 +189,15 @@ def clean_params(data): def parse_json(data): - if isinstance(data.get("filters"), string_types): + if isinstance(data.get("filters"), str): data["filters"] = json.loads(data["filters"]) - if isinstance(data.get("or_filters"), string_types): + if isinstance(data.get("or_filters"), str): data["or_filters"] = json.loads(data["or_filters"]) - if isinstance(data.get("fields"), string_types): + if isinstance(data.get("fields"), str): data["fields"] = json.loads(data["fields"]) - if isinstance(data.get("docstatus"), string_types): + if isinstance(data.get("docstatus"), str): data["docstatus"] = json.loads(data["docstatus"]) - if isinstance(data.get("save_user_settings"), string_types): + if isinstance(data.get("save_user_settings"), str): data["save_user_settings"] = json.loads(data["save_user_settings"]) else: data["save_user_settings"] = True @@ -311,7 +309,7 @@ def export_query(): for r in data: # encode only unicode type strings and not int, floats etc. writer.writerow([handle_html(frappe.as_unicode(v)) \ - if isinstance(v, string_types) else v for v in r]) + if isinstance(v, str) else v for v in r]) f.seek(0) frappe.response['result'] = cstr(f.read()) @@ -540,7 +538,7 @@ def build_match_conditions(doctype, user=None, as_condition=True): return match_conditions def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with_match_conditions=False): - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) if filters: @@ -549,7 +547,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with filters = filters.items() flt = [] for f in filters: - if isinstance(f[1], string_types) and f[1][0] == '!': + if isinstance(f[1], str) and f[1][0] == '!': flt.append([doctype, f[0], '!=', f[1][1:]]) elif isinstance(f[1], (list, tuple)) and \ f[1][0] in (">", "<", ">=", "<=", "!=", "like", "not like", "in", "not in", "between"): diff --git a/frappe/desk/search.py b/frappe/desk/search.py index 3c9109eca9..040a8c2118 100644 --- a/frappe/desk/search.py +++ b/frappe/desk/search.py @@ -2,12 +2,10 @@ # MIT License. See license.txt # Search -from __future__ import unicode_literals import frappe, json from frappe.utils import cstr, unique, cint from frappe.permissions import has_permission from frappe import _, is_whitelisted -from six import string_types import re import wrapt @@ -62,7 +60,7 @@ def search_widget(doctype, txt, query=None, searchfield=None, start=0, start = cint(start) - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) if searchfield: diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 6f0d7d3d5f..66acde4cb2 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py index b05aef7639..3fb539398a 100644 --- a/frappe/email/__init__.py +++ b/frappe/email/__init__.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.desk.reportview import build_match_conditions diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index 6f1cd8eebd..f30279e308 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -2,8 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import calendar from datetime import timedelta @@ -245,6 +243,7 @@ def send_monthly(): def make_links(columns, data): for row in data: + doc_name = row.get('name') for col in columns: if col.fieldtype == "Link" and col.options != "Currency": if col.options and row.get(col.fieldname): @@ -253,8 +252,9 @@ def make_links(columns, data): if col.options and row.get(col.fieldname) and row.get(col.options): row[col.fieldname] = get_link_to_form(row[col.options], row[col.fieldname]) elif col.fieldtype == "Currency" and row.get(col.fieldname): - row[col.fieldname] = frappe.format_value(row[col.fieldname], col) - + doc = frappe.get_doc(col.parent, doc_name) if doc_name else None + # Pass the Document to get the currency based on docfield option + row[col.fieldname] = frappe.format_value(row[col.fieldname], col, doc=doc) return columns, data def update_field_types(columns): @@ -262,4 +262,4 @@ def update_field_types(columns): if col.fieldtype in ("Link", "Dynamic Link", "Currency") and col.options != "Currency": col.fieldtype = "Data" col.options = "" - return columns \ No newline at end of file + return columns diff --git a/frappe/email/doctype/auto_email_report/test_auto_email_report.py b/frappe/email/doctype/auto_email_report/test_auto_email_report.py index e656ff18f7..211a141ec0 100644 --- a/frappe/email/doctype/auto_email_report/test_auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/test_auto_email_report.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import json import unittest diff --git a/frappe/email/doctype/document_follow/document_follow.py b/frappe/email/doctype/document_follow/document_follow.py index aaabffab6b..a04f8ef4c2 100644 --- a/frappe/email/doctype/document_follow/document_follow.py +++ b/frappe/email/doctype/document_follow/document_follow.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class DocumentFollow(Document): diff --git a/frappe/email/doctype/document_follow/test_document_follow.py b/frappe/email/doctype/document_follow/test_document_follow.py index 38aa870232..456c0931f8 100644 --- a/frappe/email/doctype/document_follow/test_document_follow.py +++ b/frappe/email/doctype/document_follow/test_document_follow.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest import frappe.desk.form.document_follow as document_follow diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 36b662bb39..ecd59f42bb 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -1,34 +1,25 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -from __future__ import unicode_literals, print_function -import frappe +import email.utils +import functools import imaplib -import re -import json import socket import time -import functools - -import email.utils - -from frappe import _, are_emails_muted -from frappe.model.document import Document -from frappe.utils import (validate_email_address, cint, cstr, get_datetime, - DATE_FORMAT, strip, comma_or, sanitize_html, add_days, parse_addr) -from frappe.utils.user import is_system_user -from frappe.utils.jinja import render_template -from frappe.email.smtp import SMTPServer -from frappe.email.receive import EmailServer, Email -from poplib import error_proto -from dateutil.relativedelta import relativedelta from datetime import datetime, timedelta +from poplib import error_proto + +import frappe +from frappe import _, are_emails_muted, safe_encode from frappe.desk.form import assign_to -from frappe.utils.user import get_system_managers -from frappe.utils.background_jobs import enqueue, get_jobs -from frappe.utils.html_utils import clean_email_html -from frappe.utils.error import raise_error_on_no_output +from frappe.email.receive import EmailServer, InboundMail, SentEmailInInboxError +from frappe.email.smtp import SMTPServer from frappe.email.utils import get_port +from frappe.model.document import Document +from frappe.utils import cint, comma_or, cstr, parse_addr, validate_email_address +from frappe.utils.background_jobs import enqueue, get_jobs +from frappe.utils.error import raise_error_on_no_output +from frappe.utils.jinja import render_template +from frappe.utils.user import get_system_managers OUTGOING_EMAIL_ACCOUNT_MISSING = _("Please setup default Email Account from Setup > Email > Email Account") @@ -430,89 +421,74 @@ class EmailAccount(Document): def receive(self, test_mails=None): """Called by scheduler to receive emails from this EMail account using POP3/IMAP.""" - def get_seen(status): - if not status: - return None - seen = 1 if status == "SEEN" else 0 - return seen + exceptions = [] + inbound_mails = self.get_inbound_mails(test_mails=test_mails) + for mail in inbound_mails: + try: + communication = mail.process() + frappe.db.commit() + # If email already exists in the system + # then do not send notifications for the same email. + if communication and mail.flags.is_new_communication: + # notify all participants of this thread + if self.enable_auto_reply: + self.send_auto_reply(communication, mail) - if self.enable_incoming: - uid_list = [] - exceptions = [] - seen_status = [] - uid_reindexed = False - email_server = None - - if frappe.local.flags.in_test: - incoming_mails = test_mails or [] + communication.send_email(is_inbound_mail_communcation=True) + except SentEmailInInboxError: + frappe.db.rollback() + except Exception: + frappe.db.rollback() + frappe.log_error('email_account.receive') + if self.use_imap: + self.handle_bad_emails(mail.uid, mail.raw_message, frappe.get_traceback()) + exceptions.append(frappe.get_traceback()) else: - email_sync_rule = self.build_email_sync_rule() + frappe.db.commit() - try: - email_server = self.get_incoming_server(in_receive=True, email_sync_rule=email_sync_rule) - except Exception: - frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name)) + #notify if user is linked to account + if len(inbound_mails)>0 and not frappe.local.flags.in_test: + frappe.publish_realtime('new_email', + {"account":self.email_account_name, "number":len(inbound_mails)} + ) - if not email_server: - return + if exceptions: + raise Exception(frappe.as_json(exceptions)) - emails = email_server.get_messages() - if not emails: - return + def get_inbound_mails(self, test_mails=None): + """retrive and return inbound mails. - incoming_mails = emails.get("latest_messages", []) - uid_list = emails.get("uid_list", []) - seen_status = emails.get("seen_status", []) - uid_reindexed = emails.get("uid_reindexed", False) + """ + if frappe.local.flags.in_test: + return [InboundMail(msg, self) for msg in test_mails or []] - for idx, msg in enumerate(incoming_mails): - uid = None if not uid_list else uid_list[idx] - self.flags.notify = True + if not self.enable_incoming: + return [] - try: - args = { - "uid": uid, - "seen": None if not seen_status else get_seen(seen_status.get(uid, None)), - "uid_reindexed": uid_reindexed - } - communication = self.insert_communication(msg, args=args) + email_sync_rule = self.build_email_sync_rule() + try: + email_server = self.get_incoming_server(in_receive=True, email_sync_rule=email_sync_rule) + messages = email_server.get_messages() or {} + except Exception: + frappe.log_error(title=_("Error while connecting to email account {0}").format(self.name)) + return [] - except SentEmailInInbox: - frappe.db.rollback() + mails = [] + for index, message in enumerate(messages.get("latest_messages", [])): + uid = messages['uid_list'][index] if messages.get('uid_list') else None + seen_status = 1 if messages.get('seen_status', {}).get(uid)=='SEEN' else 0 + mails.append(InboundMail(message, self, uid, seen_status)) - except Exception: - frappe.db.rollback() - frappe.log_error('email_account.receive') - if self.use_imap: - self.handle_bad_emails(email_server, uid, msg, frappe.get_traceback()) - exceptions.append(frappe.get_traceback()) + return mails - else: - frappe.db.commit() - if communication and self.flags.notify: - - # If email already exists in the system - # then do not send notifications for the same email. - - attachments = [] - - if hasattr(communication, '_attachments'): - attachments = [d.file_name for d in communication._attachments] - - communication.notify(attachments=attachments, fetched_from_email_account=True) - - #notify if user is linked to account - if len(incoming_mails)>0 and not frappe.local.flags.in_test: - frappe.publish_realtime('new_email', {"account":self.email_account_name, "number":len(incoming_mails)}) - - if exceptions: - raise Exception(frappe.as_json(exceptions)) - - def handle_bad_emails(self, email_server, uid, raw, reason): - if email_server and cint(email_server.settings.use_imap): + def handle_bad_emails(self, uid, raw, reason): + if cint(self.use_imap): import email try: - mail = email.message_from_string(raw) + if isinstance(raw, bytes): + mail = email.message_from_bytes(raw) + else: + mail = email.message_from_string(raw) message_id = mail.get('Message-ID') except Exception: @@ -524,275 +500,18 @@ class EmailAccount(Document): "reason":reason, "message_id": message_id, "doctype": "Unhandled Email", - "email_account": email_server.settings.email_account + "email_account": self.name }) unhandled_email.insert(ignore_permissions=True) frappe.db.commit() - def insert_communication(self, msg, args=None): - if isinstance(msg, list): - raw, uid, seen = msg - else: - raw = msg - uid = -1 - seen = 0 - if isinstance(args, dict): - if args.get("uid", -1): uid = args.get("uid", -1) - if args.get("seen", 0): seen = args.get("seen", 0) - - email = Email(raw) - - if email.from_email == self.email_id and not email.mail.get("Reply-To"): - # gmail shows sent emails in inbox - # and we don't want emails sent by us to be pulled back into the system again - # dont count emails sent by the system get those - if frappe.flags.in_test: - print('WARN: Cannot pull email. Sender sames as recipient inbox') - raise SentEmailInInbox - - if email.message_id: - # https://stackoverflow.com/a/18367248 - names = frappe.db.sql("""SELECT DISTINCT `name`, `creation` FROM `tabCommunication` - WHERE `message_id`='{message_id}' - ORDER BY `creation` DESC LIMIT 1""".format( - message_id=email.message_id - ), as_dict=True) - - if names: - name = names[0].get("name") - # email is already available update communication uid instead - frappe.db.set_value("Communication", name, "uid", uid, update_modified=False) - - self.flags.notify = False - - return frappe.get_doc("Communication", name) - - if email.content_type == 'text/html': - email.content = clean_email_html(email.content) - - communication = frappe.get_doc({ - "doctype": "Communication", - "subject": email.subject, - "content": email.content, - 'text_content': email.text_content, - "sent_or_received": "Received", - "sender_full_name": email.from_real_name, - "sender": email.from_email, - "recipients": email.mail.get("To"), - "cc": email.mail.get("CC"), - "email_account": self.name, - "communication_medium": "Email", - "uid": int(uid or -1), - "message_id": email.message_id, - "communication_date": email.date, - "has_attachment": 1 if email.attachments else 0, - "seen": seen or 0 - }) - - self.set_thread(communication, email) - if communication.seen: - # get email account user and set communication as seen - users = frappe.get_all("User Email", filters={ "email_account": self.name }, - fields=["parent"]) - users = list(set([ user.get("parent") for user in users ])) - communication._seen = json.dumps(users) - - communication.flags.in_receive = True - communication.insert(ignore_permissions=True) - - # save attachments - communication._attachments = email.save_attachments_in_doc(communication) - - # replace inline images - dirty = False - for file in communication._attachments: - if file.name in email.cid_map and email.cid_map[file.name]: - dirty = True - - email.content = email.content.replace("cid:{0}".format(email.cid_map[file.name]), - file.file_url) - - if dirty: - # not sure if using save() will trigger anything - communication.db_set("content", sanitize_html(email.content)) - - # notify all participants of this thread - if self.enable_auto_reply and getattr(communication, "is_first", False): - self.send_auto_reply(communication, email) - - return communication - - def set_thread(self, communication, email): - """Appends communication to parent based on thread ID. Will extract - parent communication and will link the communication to the reference of that - communication. Also set the status of parent transaction to Open or Replied. - - If no thread id is found and `append_to` is set for the email account, - it will create a new parent transaction (e.g. Issue)""" - parent = None - - parent = self.find_parent_from_in_reply_to(communication, email) - - if not parent and self.append_to: - self.set_sender_field_and_subject_field() - - if not parent and self.append_to: - parent = self.find_parent_based_on_subject_and_sender(communication, email) - - if not parent and self.append_to and self.append_to!="Communication": - parent = self.create_new_parent(communication, email) - - if parent: - communication.reference_doctype = parent.doctype - communication.reference_name = parent.name - - # check if message is notification and disable notifications for this message - isnotification = email.mail.get("isnotification") - if isnotification: - if "notification" in isnotification: - communication.unread_notification_sent = 1 - - def set_sender_field_and_subject_field(self): - '''Identify the sender and subject fields from the `append_to` DocType''' - # set subject_field and sender_field - meta = frappe.get_meta(self.append_to) - self.subject_field = None - self.sender_field = None - - if hasattr(meta, "subject_field"): - self.subject_field = meta.subject_field - - if hasattr(meta, "sender_field"): - self.sender_field = meta.sender_field - - def find_parent_based_on_subject_and_sender(self, communication, email): - '''Find parent document based on subject and sender match''' - parent = None - - if self.append_to and self.sender_field: - if self.subject_field: - if '#' in email.subject: - # try and match if ID is found - # document ID is appended to subject - # example "Re: Your email (#OPP-2020-2334343)" - parent_id = email.subject.rsplit('#', 1)[-1].strip(' ()') - if parent_id: - parent = frappe.db.get_all(self.append_to, filters = dict(name = parent_id), - fields = 'name') - - if not parent: - # try and match by subject and sender - # if sent by same sender with same subject, - # append it to old coversation - subject = frappe.as_unicode(strip(re.sub(r"(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*", - "", email.subject, 0, flags=re.IGNORECASE))) - - parent = frappe.db.get_all(self.append_to, filters={ - self.sender_field: email.from_email, - self.subject_field: ("like", "%{0}%".format(subject)), - "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) - }, fields = "name", limit = 1) - - if not parent and len(subject) > 10 and is_system_user(email.from_email): - # match only subject field - # when the from_email is of a user in the system - # and subject is atleast 10 chars long - parent = frappe.db.get_all(self.append_to, filters={ - self.subject_field: ("like", "%{0}%".format(subject)), - "creation": (">", (get_datetime() - relativedelta(days=60)).strftime(DATE_FORMAT)) - }, fields = "name", limit = 1) - - - - if parent: - parent = frappe._dict(doctype=self.append_to, name=parent[0].name) - return parent - - def create_new_parent(self, communication, email): - '''If no parent found, create a new reference document''' - - # no parent found, but must be tagged - # insert parent type doc - parent = frappe.new_doc(self.append_to) - - if self.subject_field: - parent.set(self.subject_field, frappe.as_unicode(email.subject)[:140]) - - if self.sender_field: - parent.set(self.sender_field, frappe.as_unicode(email.from_email)) - - if parent.meta.has_field("email_account"): - parent.email_account = self.name - - parent.flags.ignore_mandatory = True - - try: - parent.insert(ignore_permissions=True) - except frappe.DuplicateEntryError: - # try and find matching parent - parent_name = frappe.db.get_value(self.append_to, {self.sender_field: email.from_email}) - if parent_name: - parent.name = parent_name - else: - parent = None - - # NOTE if parent isn't found and there's no subject match, it is likely that it is a new conversation thread and hence is_first = True - communication.is_first = True - - return parent - - def find_parent_from_in_reply_to(self, communication, email): - '''Returns parent reference if embedded in In-Reply-To header - - Message-ID is formatted as `{message_id}@{site}`''' - parent = None - in_reply_to = (email.mail.get("In-Reply-To") or "").strip(" <>") - - if in_reply_to: - if "@{0}".format(frappe.local.site) in in_reply_to: - # reply to a communication sent from the system - email_queue = frappe.db.get_value('Email Queue', dict(message_id=in_reply_to), ['communication','reference_doctype', 'reference_name']) - if email_queue: - parent_communication, parent_doctype, parent_name = email_queue - if parent_communication: - communication.in_reply_to = parent_communication - else: - reference, domain = in_reply_to.split("@", 1) - parent_doctype, parent_name = 'Communication', reference - - if frappe.db.exists(parent_doctype, parent_name): - parent = frappe._dict(doctype=parent_doctype, name=parent_name) - - # set in_reply_to of current communication - if parent_doctype=='Communication': - # communication.in_reply_to = email_queue.communication - - if parent.reference_name: - # the true parent is the communication parent - parent = frappe.get_doc(parent.reference_doctype, - parent.reference_name) - else: - comm = frappe.db.get_value('Communication', - dict( - message_id=in_reply_to, - creation=['>=', add_days(get_datetime(), -30)]), - ['reference_doctype', 'reference_name'], as_dict=1) - if comm: - parent = frappe._dict(doctype=comm.reference_doctype, name=comm.reference_name) - - return parent - def send_auto_reply(self, communication, email): """Send auto reply if set.""" from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts - if self.enable_auto_reply: set_incoming_outgoing_accounts(communication) - if self.send_unsubscribe_message: - unsubscribe_message = _("Leave this conversation") - else: - unsubscribe_message = "" + unsubscribe_message = (self.send_unsubscribe_message and _("Leave this conversation")) or "" frappe.sendmail(recipients = [email.from_email], sender = self.email_id, @@ -851,8 +570,8 @@ class EmailAccount(Document): email_server.update_flag(uid_list=uid_list) # mark communication as read - docnames = ",".join([ "'%s'"%flag.get("communication") for flag in flags \ - if flag.get("action") == "Read" ]) + docnames = ",".join("'%s'"%flag.get("communication") for flag in flags \ + if flag.get("action") == "Read") self.set_communication_seen_status(docnames, seen=1) # mark communication as unread @@ -882,7 +601,6 @@ class EmailAccount(Document): def append_email_to_sent_folder(self, message): - email_server = None try: email_server = self.get_incoming_server(in_receive=True) @@ -896,7 +614,8 @@ class EmailAccount(Document): if email_server.imap: try: - email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message.encode()) + message = safe_encode(message) + email_server.imap.append("Sent", "\\Seen", imaplib.Time2Internaldate(time.time()), message) except Exception: frappe.log_error() diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index f87ee32bb1..35cacac45a 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -1,45 +1,56 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -import frappe, os -import unittest, email +import os +import email +import unittest +from datetime import datetime, timedelta +from frappe.email.receive import InboundMail, SentEmailInInboxError, Email +from frappe.email.email_body import get_message_id +import frappe from frappe.test_runner import make_test_records +from frappe.core.doctype.communication.email import make +from frappe.desk.form.load import get_attachments +from frappe.email.doctype.email_account.email_account import notify_unreplied 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 -from frappe.email.doctype.email_account.email_account import notify_unreplied -from datetime import datetime, timedelta + class TestEmailAccount(unittest.TestCase): + @classmethod + def setUpClass(cls): + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.db_set("enable_incoming", 1) + email_account.db_set("enable_auto_reply", 1) + + @classmethod + def tearDownClass(cls): + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.db_set("enable_incoming", 0) + def setUp(self): frappe.flags.mute_emails = False frappe.flags.sent_mail = None - - email_account = frappe.get_doc("Email Account", "_Test Email Account 1") - email_account.db_set("enable_incoming", 1) frappe.db.sql('delete from `tabEmail Queue`') + frappe.db.sql('delete from `tabUnhandled Email`') - def tearDown(self): - email_account = frappe.get_doc("Email Account", "_Test Email Account 1") - email_account.db_set("enable_incoming", 0) + def get_test_mail(self, fname): + with open(os.path.join(os.path.dirname(__file__), "test_mails", fname), "r") as f: + return f.read() def test_incoming(self): cleanup("test_sender@example.com") - with open(os.path.join(os.path.dirname(__file__), "test_mails", "incoming-1.raw"), "r") as f: - test_mails = [f.read()] + test_mails = [self.get_test_mail('incoming-1.raw')] email_account = frappe.get_doc("Email Account", "_Test Email Account 1") email_account.receive(test_mails=test_mails) comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) self.assertTrue("test_receiver@example.com" in comm.recipients) - # check if todo is created self.assertTrue(frappe.db.get_value(comm.reference_doctype, comm.reference_name, "name")) @@ -88,7 +99,7 @@ class TestEmailAccount(unittest.TestCase): email_account.receive(test_mails=test_mails) comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) - self.assertTrue("From: \"Microsoft Outlook\" <test_sender@example.com>" in comm.content) + self.assertTrue("From: "Microsoft Outlook" <test_sender@example.com>" in comm.content) self.assertTrue("This is an e-mail message sent automatically by Microsoft Outlook while" in comm.content) def test_incoming_attached_email_from_outlook_layers(self): @@ -101,7 +112,7 @@ class TestEmailAccount(unittest.TestCase): email_account.receive(test_mails=test_mails) comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) - self.assertTrue("From: \"Microsoft Outlook\" <test_sender@example.com>" in comm.content) + self.assertTrue("From: "Microsoft Outlook" <test_sender@example.com>" in comm.content) self.assertTrue("This is an e-mail message sent automatically by Microsoft Outlook while" in comm.content) def test_outgoing(self): @@ -166,7 +177,6 @@ class TestEmailAccount(unittest.TestCase): comm_list = frappe.get_all("Communication", filters={"sender":"test_sender@example.com"}, fields=["name", "reference_doctype", "reference_name"]) - # both communications attached to the same reference self.assertEqual(comm_list[0].reference_doctype, comm_list[1].reference_doctype) self.assertEqual(comm_list[0].reference_name, comm_list[1].reference_name) @@ -199,6 +209,215 @@ class TestEmailAccount(unittest.TestCase): self.assertEqual(comm_list[0].reference_doctype, event.doctype) self.assertEqual(comm_list[0].reference_name, event.name) + def test_auto_reply(self): + cleanup("test_sender@example.com") + + test_mails = [self.get_test_mail('incoming-1.raw')] + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.receive(test_mails=test_mails) + + comm = frappe.get_doc("Communication", {"sender": "test_sender@example.com"}) + self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": comm.reference_doctype, + "reference_name": comm.reference_name})) + + def test_handle_bad_emails(self): + mail_content = self.get_test_mail(fname="incoming-1.raw") + message_id = Email(mail_content).mail.get('Message-ID') + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.handle_bad_emails(uid=-1, raw=mail_content, reason="Testing") + self.assertTrue(frappe.db.get_value("Unhandled Email", {'message_id': message_id})) + +class TestInboundMail(unittest.TestCase): + @classmethod + def setUpClass(cls): + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.db_set("enable_incoming", 1) + + @classmethod + def tearDownClass(cls): + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + email_account.db_set("enable_incoming", 0) + + def setUp(self): + cleanup() + frappe.db.sql('delete from `tabEmail Queue`') + frappe.db.sql('delete from `tabToDo`') + + def get_test_mail(self, fname): + with open(os.path.join(os.path.dirname(__file__), "test_mails", fname), "r") as f: + return f.read() + + def new_doc(self, doctype, **data): + doc = frappe.new_doc(doctype) + for field, value in data.items(): + setattr(doc, field, value) + doc.insert() + return doc + + def new_communication(self, **kwargs): + defaults = { + 'subject': "Test Subject" + } + d = {**defaults, **kwargs} + return self.new_doc('Communication', **d) + + def new_email_queue(self, **kwargs): + defaults = { + 'message_id': get_message_id().strip(" <>") + } + d = {**defaults, **kwargs} + return self.new_doc('Email Queue', **d) + + def new_todo(self, **kwargs): + defaults = { + 'description': "Description" + } + d = {**defaults, **kwargs} + return self.new_doc('ToDo', **d) + + def test_self_sent_mail(self): + """Check that we raise SentEmailInInboxError if the inbound mail is self sent mail. + """ + mail_content = self.get_test_mail(fname="incoming-self-sent.raw") + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 1, 1) + with self.assertRaises(SentEmailInInboxError): + inbound_mail.process() + + def test_mail_exist_validation(self): + """Do not create communication record if the mail is already downloaded into the system. + """ + mail_content = self.get_test_mail(fname="incoming-1.raw") + message_id = Email(mail_content).message_id + # Create new communication record in DB + communication = self.new_communication(message_id=message_id) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + new_communiction = inbound_mail.process() + + # Make sure that uid is changed to new uid + self.assertEqual(new_communiction.uid, 12345) + self.assertEqual(communication.name, new_communiction.name) + + def test_find_parent_email_queue(self): + """If the mail is reply to the already sent mail, there will be a email queue record. + """ + # Create email queue record + queue_record = self.new_email_queue() + + mail_content = self.get_test_mail(fname="reply-4.raw").replace( + "{{ message_id }}", queue_record.message_id + ) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + parent_queue = inbound_mail.parent_email_queue() + self.assertEqual(queue_record.name, parent_queue.name) + + def test_find_parent_communication_through_queue(self): + """Find parent communication of an inbound mail. + Cases where parent communication does exist: + 1. No parent communication is the mail is not a reply. + + Cases where parent communication does not exist: + 2. If mail is not a reply to system sent mail, then there can exist co + """ + # Create email queue record + communication = self.new_communication() + queue_record = self.new_email_queue(communication=communication.name) + mail_content = self.get_test_mail(fname="reply-4.raw").replace( + "{{ message_id }}", queue_record.message_id + ) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + parent_communication = inbound_mail.parent_communication() + self.assertEqual(parent_communication.name, communication.name) + + def test_find_parent_communication_for_self_reply(self): + """If the inbound email is a reply but not reply to system sent mail. + + Ex: User replied to his/her mail. + """ + message_id = "new-message-id" + mail_content = self.get_test_mail(fname="reply-4.raw").replace( + "{{ message_id }}", message_id + ) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + parent_communication = inbound_mail.parent_communication() + self.assertFalse(parent_communication) + + communication = self.new_communication(message_id=message_id) + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + parent_communication = inbound_mail.parent_communication() + self.assertEqual(parent_communication.name, communication.name) + + def test_find_parent_communication_from_header(self): + """Incase of header contains parent communication name + """ + communication = self.new_communication() + mail_content = self.get_test_mail(fname="reply-4.raw").replace( + "{{ message_id }}", f"<{communication.name}@{frappe.local.site}>" + ) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + parent_communication = inbound_mail.parent_communication() + self.assertEqual(parent_communication.name, communication.name) + + def test_reference_document(self): + # Create email queue record + todo = self.new_todo() + # communication = self.new_communication(reference_doctype='ToDo', reference_name=todo.name) + queue_record = self.new_email_queue(reference_doctype='ToDo', reference_name=todo.name) + mail_content = self.get_test_mail(fname="reply-4.raw").replace( + "{{ message_id }}", queue_record.message_id + ) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + reference_doc = inbound_mail.reference_document() + self.assertEqual(todo.name, reference_doc.name) + + def test_reference_document_by_record_name_in_subject(self): + # Create email queue record + todo = self.new_todo() + + mail_content = self.get_test_mail(fname="incoming-subject-placeholder.raw").replace( + "{{ subject }}", f"RE: (#{todo.name})" + ) + + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + reference_doc = inbound_mail.reference_document() + self.assertEqual(todo.name, reference_doc.name) + + def test_reference_document_by_subject_match(self): + subject = "New todo" + todo = self.new_todo(sender='test_sender@example.com', description=subject) + + mail_content = self.get_test_mail(fname="incoming-subject-placeholder.raw").replace( + "{{ subject }}", f"RE: {subject}" + ) + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + reference_doc = inbound_mail.reference_document() + self.assertEqual(todo.name, reference_doc.name) + + def test_create_communication_from_mail(self): + # Create email queue record + mail_content = self.get_test_mail(fname="incoming-2.raw") + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + communication = inbound_mail.process() + self.assertTrue(communication.is_first) + self.assertTrue(communication._attachments) + def cleanup(sender=None): filters = {} if sender: @@ -207,4 +426,4 @@ def cleanup(sender=None): names = frappe.get_list("Communication", filters=filters, fields=["name"]) for name in names: frappe.delete_doc_if_exists("Communication", name.name) - frappe.delete_doc_if_exists("Communication Link", {"parent": name.name}) \ No newline at end of file + frappe.delete_doc_if_exists("Communication Link", {"parent": name.name}) diff --git a/frappe/email/doctype/email_account/test_mails/incoming-self-sent.raw b/frappe/email/doctype/email_account/test_mails/incoming-self-sent.raw new file mode 100644 index 0000000000..a16eecccd5 --- /dev/null +++ b/frappe/email/doctype/email_account/test_mails/incoming-self-sent.raw @@ -0,0 +1,91 @@ +Delivered-To: test_receiver@example.com +Received: by 10.96.153.227 with SMTP id vj3csp416144qdb; + Mon, 15 Sep 2014 03:35:07 -0700 (PDT) +X-Received: by 10.66.119.103 with SMTP id kt7mr36981968pab.95.1410777306321; + Mon, 15 Sep 2014 03:35:06 -0700 (PDT) +Return-Path: +Received: from mail-pa0-x230.google.com (mail-pa0-x230.google.com [2607:f8b0:400e:c03::230]) + by mx.google.com with ESMTPS id dg10si22178346pdb.115.2014.09.15.03.35.06 + for + (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); + Mon, 15 Sep 2014 03:35:06 -0700 (PDT) +Received-SPF: pass (google.com: domain of test@example.com designates 2607:f8b0:400e:c03::230 as permitted sender) client-ip=2607:f8b0:400e:c03::230; +Authentication-Results: mx.google.com; + spf=pass (google.com: domain of test@example.com designates 2607:f8b0:400e:c03::230 as permitted sender) smtp.mail=test@example.com; + dkim=pass header.i=@gmail.com; + dmarc=pass (p=NONE dis=NONE) header.from=gmail.com +Received: by mail-pa0-f48.google.com with SMTP id hz1so6118714pad.21 + for ; Mon, 15 Sep 2014 03:35:06 -0700 (PDT) +DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; + d=gmail.com; s=20120113; + h=from:content-type:subject:message-id:date:to:mime-version; + bh=rwiLijtF3lfy9M6cP/7dv2Hm7NJuBwFZn1OFsN8Tlvs=; + b=x7U4Ny3Kz2ULRJ7a04NDBrBTVhP2ImIB9n3LVNGQDnDonPUM5Ro/wZcxPTVnBWZ2L1 + o1bGfP+lhBrvYUlHsd5r4FYC0Uvpad6hbzLr0DGUQgPTxW4cGKbtDEAq+BR2JWd9f803 + vdjSWdGk8w2dt2qbngTqIZkm5U2XWjICDOAYuPIseLUgCFwi9lLyOSARFB7mjAa2YL7Q + Nswk7mbWU1hbnHP6jaBb0m8QanTc7Up944HpNDRxIrB1ZHgKzYhXtx8nhnOx588ZGIAe + E6tyG8IwogR11vLkkrBhtMaOme9PohYx4F1CSTiwspmDCadEzJFGRe//lEXKmZHAYH6g + 90Zg== +X-Received: by 10.70.38.135 with SMTP id g7mr22078275pdk.100.1410777305744; + Mon, 15 Sep 2014 03:35:05 -0700 (PDT) +Return-Path: +Received: from [192.168.0.100] ([27.106.4.70]) + by mx.google.com with ESMTPSA id zr6sm11025126pbc.50.2014.09.15.03.35.02 + for + (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); + Mon, 15 Sep 2014 03:35:04 -0700 (PDT) +From: Rushabh Mehta +Content-Type: multipart/alternative; boundary="Apple-Mail=_57F71261-5C3A-43F6-918B-4438B96F61AA" +Subject: test mail 🦄🌈😎 +Message-Id: <9143999C-8456-4399-9CF1-4A2DA9DD7711@gmail.com> +Date: Mon, 15 Sep 2014 16:04:57 +0530 +To: Rushabh Mehta +Mime-Version: 1.0 (Mac OS X Mail 7.3 \(1878.6\)) +X-Mailer: Apple Mail (2.1878.6) + + +--Apple-Mail=_57F71261-5C3A-43F6-918B-4438B96F61AA +Content-Transfer-Encoding: 7bit +Content-Type: text/plain; + charset=us-ascii + +test mail + + + +@rushabh_mehta +https://erpnext.org + + +--Apple-Mail=_57F71261-5C3A-43F6-918B-4438B96F61AA +Content-Transfer-Encoding: quoted-printable +Content-Type: text/html; + charset=us-ascii + +test = +mail
+



@rushabh_mehta
+
+
= + +--Apple-Mail=_57F71261-5C3A-43F6-918B-4438B96F61AA-- diff --git a/frappe/email/doctype/email_account/test_mails/incoming-subject-placeholder.raw b/frappe/email/doctype/email_account/test_mails/incoming-subject-placeholder.raw new file mode 100644 index 0000000000..35ddf06b01 --- /dev/null +++ b/frappe/email/doctype/email_account/test_mails/incoming-subject-placeholder.raw @@ -0,0 +1,183 @@ +Return-path: +Envelope-to: test_receiver@example.com +Delivery-date: Wed, 27 Jan 2016 16:24:20 +0800 +Received: from 23-59-23-10.perm.iinet.net.au ([23.59.23.10]:62191 helo=DESKTOP7C66I2M) + by webcloud85.au.syrahost.com with esmtp (Exim 4.86) + (envelope-from ) + id 1aOLOj-002xFL-CP + for test_receiver@example.com; Wed, 27 Jan 2016 16:24:20 +0800 +From: +To: +References: +In-Reply-To: +Subject: RE: {{ subject }} +Date: Wed, 27 Jan 2016 16:24:09 +0800 +Message-ID: <000001d158dc$1b8363a0$528a2ae0$@example.com> +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_NextPart_000_0001_01D1591F.29A7DC20" +X-Mailer: Microsoft Outlook 14.0 +Thread-Index: AQJZfZxrgcB9KnMqoZ+S4Qq9hcoSeZ3+vGiQ +Content-Language: en-au + +This is a multipart message in MIME format. + +------=_NextPart_000_0001_01D1591F.29A7DC20 +Content-Type: multipart/alternative; + boundary="----=_NextPart_001_0002_01D1591F.29A7DC20" + + +------=_NextPart_001_0002_01D1591F.29A7DC20 +Content-Type: text/plain; + charset="utf-8" +Content-Transfer-Encoding: quoted-printable + +Test purely for testing with the debugger has email attached + +=20 + +From: Notification [mailto:test_receiver@example.com]=20 +Sent: Wednesday, 27 January 2016 9:30 AM +To: test_receiver@example.com +Subject: Sales Invoice: SINV-12276 + +=20 + +test no 6 sent from bench to outlook to be replied to with messaging + + + + +------=_NextPart_001_0002_01D1591F.29A7DC20 +Content-Type: text/html; + charset="utf-8" +Content-Transfer-Encoding: quoted-printable + +hi there

Test purely for testing with the debugger has email = +attached

 

From:= + = +Notification [mailto:test_receiver@example.com]
Sent: Wednesday, 27 = +January 2016 9:30 AM
To: = +test_receiver@example.com
Subject: Sales Invoice: = +SINV-12276

 

test no 3 sent from bench to outlook to be replied to with = +messaging

fizz buzz

This email was sent to test_receiver@example.= +com and copied to SuperUser

Leave this conversation = +

hi

+------=_NextPart_001_0002_01D1591F.29A7DC20-- + +------=_NextPart_000_0001_01D1591F.29A7DC20 +Content-Type: message/rfc822 +Content-Transfer-Encoding: 7bit +Content-Disposition: attachment + +Received: from 203-59-223-10.perm.iinet.net.au ([23.59.23.10]:49772 helo=DESKTOP7C66I2M) + by webcloud85.au.syrahost.com with esmtpsa (TLSv1.2:DHE-RSA-AES256-GCM-SHA384:256) + (Exim 4.86) + (envelope-from ) + id 1aOEtO-003tI4-Kv + for test_receiver@example.com; Wed, 27 Jan 2016 09:27:30 +0800 +Return-Path: +From: "Microsoft Outlook" +To: +Subject: Microsoft Outlook Test Message +MIME-Version: 1.0 +Content-Type: text/plain; + charset="utf-8" +Content-Transfer-Encoding: quoted-printable +X-Mailer: Microsoft Outlook 14.0 +Thread-Index: AdFYoeN8x8wUI/+QSoCJkp33NKPVmw== + +This is an e-mail message sent automatically by Microsoft Outlook while = +testing the settings for your account. diff --git a/frappe/email/doctype/email_domain/email_domain.py b/frappe/email/doctype/email_domain/email_domain.py index ce39523564..0856549eb7 100644 --- a/frappe/email/doctype/email_domain/email_domain.py +++ b/frappe/email/doctype/email_domain/email_domain.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document diff --git a/frappe/email/doctype/email_domain/test_email_domain.py b/frappe/email/doctype/email_domain/test_email_domain.py index 1c5306e9c2..8607151ca8 100644 --- a/frappe/email/doctype/email_domain/test_email_domain.py +++ b/frappe/email/doctype/email_domain/test_email_domain.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.test_runner import make_test_objects diff --git a/frappe/email/doctype/email_flag_queue/email_flag_queue.py b/frappe/email/doctype/email_flag_queue/email_flag_queue.py index 487ef7db50..9bb30f08b2 100644 --- a/frappe/email/doctype/email_flag_queue/email_flag_queue.py +++ b/frappe/email/doctype/email_flag_queue/email_flag_queue.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py b/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py index 644a2a8ff7..d09b823ce6 100644 --- a/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py +++ b/frappe/email/doctype/email_flag_queue/test_email_flag_queue.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/email_group/email_group.json b/frappe/email/doctype/email_group/email_group.json index c49de841e6..cb74249143 100644 --- a/frappe/email/doctype/email_group/email_group.json +++ b/frappe/email/doctype/email_group/email_group.json @@ -1,6 +1,7 @@ { "actions": [], "allow_import": 1, + "allow_rename": 1, "autoname": "field:title", "creation": "2015-03-18 06:08:32.729800", "doctype": "DocType", @@ -50,7 +51,7 @@ "link_fieldname": "email_group" } ], - "modified": "2020-09-24 16:41:55.286377", + "modified": "2021-06-15 11:25:13.556201", "modified_by": "Administrator", "module": "Email", "name": "Email Group", diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py index b19a134713..2679353edf 100755 --- a/frappe/email/doctype/email_group/email_group.py +++ b/frappe/email/doctype/email_group/email_group.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import validate_email_address @@ -105,6 +104,6 @@ def send_welcome_email(welcome_email, email, email_group): email=email, email_group=email_group ) - - message = frappe.render_template(welcome_email.response, args) + email_message = welcome_email.response or welcome_email.response_html + message = frappe.render_template(email_message, args) frappe.sendmail(email, subject=welcome_email.subject, message=message) diff --git a/frappe/email/doctype/email_group/test_email_group.py b/frappe/email/doctype/email_group/test_email_group.py index 09f4f4c32c..3e894118df 100644 --- a/frappe/email/doctype/email_group/test_email_group.py +++ b/frappe/email/doctype/email_group/test_email_group.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/email_group_member/email_group_member.py b/frappe/email/doctype/email_group_member/email_group_member.py index 23b279e755..1f9303b83e 100644 --- a/frappe/email/doctype/email_group_member/email_group_member.py +++ b/frappe/email/doctype/email_group_member/email_group_member.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_group_member/test_email_group_member.py b/frappe/email/doctype/email_group_member/test_email_group_member.py index 35259617c1..829d686400 100644 --- a/frappe/email/doctype/email_group_member/test_email_group_member.py +++ b/frappe/email/doctype/email_group_member/test_email_group_member.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index 9e2fe32250..e1e332f978 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -9,14 +9,18 @@ from rq.timeouts import JobTimeoutException import smtplib import quopri from email.parser import Parser +from email.policy import SMTPUTF8 +from html2text import html2text +from six.moves import html_parser as HTMLParser import frappe from frappe import _, safe_encode, task from frappe.model.document import Document -from frappe.email.queue import get_unsubcribed_url -from frappe.email.email_body import add_attachment -from frappe.utils import cint -from email.policy import SMTPUTF8 +from frappe.email.queue import get_unsubcribed_url, get_unsubscribe_message +from frappe.email.email_body import add_attachment, get_formatted_html, get_email +from frappe.utils import cint, split_emails, add_days, nowdate, cstr +from frappe.email.doctype.email_account.email_account import EmailAccount + MAX_RETRY_COUNT = 3 class EmailQueue(Document): @@ -41,10 +45,28 @@ class EmailQueue(Document): duplicate.set_recipients(recipients) return duplicate + @classmethod + def new(cls, doc_data, ignore_permissions=False): + data = doc_data.copy() + if not data.get('recipients'): + return + + recipients = data.pop('recipients') + doc = frappe.new_doc(cls.DOCTYPE) + doc.update(data) + doc.set_recipients(recipients) + doc.insert(ignore_permissions=ignore_permissions) + return doc + @classmethod def find(cls, name): return frappe.get_doc(cls.DOCTYPE, name) + @classmethod + def find_one_by_filters(cls, **kwargs): + name = frappe.db.get_value(cls.DOCTYPE, kwargs) + return cls.find(name) if name else None + def update_db(self, commit=False, **kwargs): frappe.db.set_value(self.DOCTYPE, self.name, kwargs) if commit: @@ -69,8 +91,6 @@ class EmailQueue(Document): return json.loads(self.attachments) if self.attachments else [] def get_email_account(self): - from frappe.email.doctype.email_account.email_account import EmailAccount - if self.email_account: return frappe.get_doc('Email Account', self.email_account) @@ -159,7 +179,14 @@ class SendMailContext: else: email_status = self.is_mail_sent_to_all() and 'Sent' email_status = email_status or (self.sent_to and 'Partially Sent') or 'Not Sent' - self.queue_doc.update_status(status = email_status, commit = True) + + update_fields = {'status': email_status} + if self.email_account_doc.is_exists_in_db(): + update_fields['email_account'] = self.email_account_doc.name + else: + update_fields['email_account'] = None + + self.queue_doc.update_status(**update_fields, commit = True) def log_exception(self, exc_type, exc_val, exc_tb): if exc_type: @@ -295,3 +322,283 @@ def send_now(name): def on_doctype_update(): """Add index in `tabCommunication` for `(reference_doctype, reference_name)`""" frappe.db.add_index('Email Queue', ('status', 'send_after', 'priority', 'creation'), 'index_bulk_flush') + +class QueueBuilder: + """Builds Email Queue from the given data + """ + def __init__(self, recipients=None, sender=None, subject=None, message=None, + text_content=None, reference_doctype=None, reference_name=None, + unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, + attachments=None, reply_to=None, cc=None, bcc=None, message_id=None, in_reply_to=None, + send_after=None, expose_recipients=None, send_priority=1, communication=None, + read_receipt=None, queue_separately=False, is_notification=False, + add_unsubscribe_link=1, inline_images=None, header=None, + print_letterhead=False, with_container=False): + """Add email to sending queue (Email Queue) + + :param recipients: List of recipients. + :param sender: Email sender. + :param subject: Email subject. + :param message: Email message. + :param text_content: Text version of email message. + :param reference_doctype: Reference DocType of caller document. + :param reference_name: Reference name of caller document. + :param send_priority: Priority for Email Queue, default 1. + :param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.queue.unsubscribe`. + :param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email + :param attachments: Attachments to be sent. + :param reply_to: Reply to be captured here (default inbox) + :param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To. + :param send_after: Send this email after the given datetime. If value is in integer, then `send_after` will be the automatically set to no of days from current date. + :param communication: Communication link to be set in Email Queue record + :param queue_separately: Queue each email separately + :param is_notification: Marks email as notification so will not trigger notifications from system + :param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. + :param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id + :param header: Append header in email (boolean) + :param with_container: Wraps email inside styled container + """ + + self._unsubscribe_method = unsubscribe_method + self._recipients = recipients + self._cc = cc + self._bcc = bcc + self._send_after = send_after + self._sender = sender + self._text_content = text_content + self._message = message + self._add_unsubscribe_link = add_unsubscribe_link + self._unsubscribe_message = unsubscribe_message + self._attachments = attachments + + self._unsubscribed_user_emails = None + self._email_account = None + + self.unsubscribe_params = unsubscribe_params + self.subject = subject + self.reference_doctype = reference_doctype + self.reference_name = reference_name + self.expose_recipients = expose_recipients + self.with_container = with_container + self.header = header + self.reply_to = reply_to + self.message_id = message_id + self.in_reply_to = in_reply_to + self.send_priority = send_priority + self.communication = communication + self.read_receipt = read_receipt + self.queue_separately = queue_separately + self.is_notification = is_notification + self.inline_images = inline_images + self.print_letterhead = print_letterhead + + @property + def unsubscribe_method(self): + return self._unsubscribe_method or '/api/method/frappe.email.queue.unsubscribe' + + def _get_emails_list(self, emails=None): + emails = split_emails(emails) if isinstance(emails, str) else (emails or []) + return [each for each in set(emails) if each] + + @property + def recipients(self): + return self._get_emails_list(self._recipients) + + @property + def cc(self): + return self._get_emails_list(self._cc) + + @property + def bcc(self): + return self._get_emails_list(self._bcc) + + @property + def send_after(self): + if isinstance(self._send_after, int): + return add_days(nowdate(), self._send_after) + return self._send_after + + @property + def sender(self): + if not self._sender or self._sender == "Administrator": + email_account = self.get_outgoing_email_account() + return email_account.default_sender + return self._sender + + def email_text_content(self): + unsubscribe_msg = self.unsubscribe_message() + unsubscribe_text_message = (unsubscribe_msg and unsubscribe_msg.text) or '' + + if self._text_content: + return self._text_content + unsubscribe_text_message + + try: + text_content = html2text(self._message) + except HTMLParser.HTMLParseError: + text_content = "See html attachment" + return text_content + unsubscribe_text_message + + def email_html_content(self): + email_account = self.get_outgoing_email_account() + return get_formatted_html(self.subject, self._message, header=self.header, + email_account=email_account, unsubscribe_link=self.unsubscribe_message(), + with_container=self.with_container) + + def should_include_unsubscribe_link(self): + return (self._add_unsubscribe_link == 1 + and self.reference_doctype + and (self._unsubscribe_message or self.reference_doctype=="Newsletter")) + + def unsubscribe_message(self): + if self.should_include_unsubscribe_link(): + return get_unsubscribe_message(self._unsubscribe_message, self.expose_recipients) + + def get_outgoing_email_account(self): + if self._email_account: + return self._email_account + + self._email_account = EmailAccount.find_outgoing( + match_by_doctype=self.reference_doctype, match_by_email=self._sender, _raise_error=True) + return self._email_account + + def get_unsubscribed_user_emails(self): + if self._unsubscribed_user_emails is not None: + return self._unsubscribed_user_emails + + all_ids = tuple(set(self.recipients + self.cc)) + + unsubscribed = frappe.db.sql_list(''' + SELECT + distinct email + from + `tabEmail Unsubscribe` + where + email in %(all_ids)s + and ( + ( + reference_doctype = %(reference_doctype)s + and reference_name = %(reference_name)s + ) + or global_unsubscribe = 1 + ) + ''', { + 'all_ids': all_ids, + 'reference_doctype': self.reference_doctype, + 'reference_name': self.reference_name, + }) + + self._unsubscribed_user_emails = unsubscribed or [] + return self._unsubscribed_user_emails + + def final_recipients(self): + unsubscribed_emails = self.get_unsubscribed_user_emails() + return [mail_id for mail_id in self.recipients if mail_id not in unsubscribed_emails] + + def final_cc(self): + unsubscribed_emails = self.get_unsubscribed_user_emails() + return [mail_id for mail_id in self.cc if mail_id not in unsubscribed_emails] + + def get_attachments(self): + attachments = [] + if self._attachments: + # store attachments with fid or print format details, to be attached on-demand later + for att in self._attachments: + if att.get('fid'): + attachments.append(att) + elif att.get("print_format_attachment") == 1: + if not att.get('lang', None): + att['lang'] = frappe.local.lang + att['print_letterhead'] = self.print_letterhead + attachments.append(att) + return attachments + + def prepare_email_content(self): + mail = get_email(recipients=self.final_recipients(), + sender=self.sender, + subject=self.subject, + formatted=self.email_html_content(), + text_content=self.email_text_content(), + attachments=self._attachments, + reply_to=self.reply_to, + cc=self.final_cc(), + bcc=self.bcc, + email_account=self.get_outgoing_email_account(), + expose_recipients=self.expose_recipients, + inline_images=self.inline_images, + header=self.header) + + mail.set_message_id(self.message_id, self.is_notification) + if self.read_receipt: + mail.msg_root["Disposition-Notification-To"] = self.sender + if self.in_reply_to: + mail.set_in_reply_to(self.in_reply_to) + return mail + + def process(self, send_now=False): + """Build and return the email queues those are created. + + Sends email incase if it is requested to send now. + """ + final_recipients = self.final_recipients() + queue_separately = (final_recipients and self.queue_separately) or len(final_recipients) > 20 + if not (final_recipients + self.final_cc()): + return [] + + email_queues = [] + queue_data = self.as_dict(include_recipients=False) + if not queue_data: + return [] + + if not queue_separately: + recipients = list(set(final_recipients + self.final_cc() + self.bcc)) + q = EmailQueue.new({**queue_data, **{'recipients': recipients}}, ignore_permissions=True) + email_queues.append(q) + else: + for r in final_recipients: + recipients = [r] if email_queues else list(set([r] + self.final_cc() + self.bcc)) + q = EmailQueue.new({**queue_data, **{'recipients': recipients}}, ignore_permissions=True) + email_queues.append(q) + + if send_now: + for doc in email_queues: + doc.send() + return email_queues + + def as_dict(self, include_recipients=True): + email_account = self.get_outgoing_email_account() + email_account_name = email_account and email_account.is_exists_in_db() and email_account.name + + mail = self.prepare_email_content() + try: + mail_to_string = cstr(mail.as_string()) + except frappe.InvalidEmailAddressError: + # bad Email Address - don't add to queue + frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}, \nTraceback: {2} ' + .format(self.sender, ', '.join(self.final_recipients()), traceback.format_exc()), + 'Email Not Sent' + ) + return + + d = { + 'priority': self.send_priority, + 'attachments': json.dumps(self.get_attachments()), + 'message_id': mail.msg_root["Message-Id"].strip(" <>"), + 'message': mail_to_string, + 'sender': self.sender, + 'reference_doctype': self.reference_doctype, + 'reference_name': self.reference_name, + 'add_unsubscribe_link': self._add_unsubscribe_link, + 'unsubscribe_method': self.unsubscribe_method, + 'unsubscribe_params': self.unsubscribe_params, + 'expose_recipients': self.expose_recipients, + 'communication': self.communication, + 'send_after': self.send_after, + 'show_as_cc': ",".join(self.final_cc()), + 'show_as_bcc': ','.join(self.bcc), + 'email_account': email_account_name or None + } + + if include_recipients: + d['recipients'] = self.final_recipients() + + return d diff --git a/frappe/email/doctype/email_queue/test_email_queue.py b/frappe/email/doctype/email_queue/test_email_queue.py index 7cd79f9259..b76d6347b9 100644 --- a/frappe/email/doctype/email_queue/test_email_queue.py +++ b/frappe/email/doctype/email_queue/test_email_queue.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py index 3f07ec58f3..055bdb3fc1 100644 --- a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py +++ b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_rule/email_rule.py b/frappe/email/doctype/email_rule/email_rule.py index 220798bbdc..9807724ef1 100644 --- a/frappe/email/doctype/email_rule/email_rule.py +++ b/frappe/email/doctype/email_rule/email_rule.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/email_rule/test_email_rule.py b/frappe/email/doctype/email_rule/test_email_rule.py index 3c7f9c83e6..b2213f7405 100644 --- a/frappe/email/doctype/email_rule/test_email_rule.py +++ b/frappe/email/doctype/email_rule/test_email_rule.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/email_template/email_template.py b/frappe/email/doctype/email_template/email_template.py index 6708e9dd3f..4711451fd2 100644 --- a/frappe/email/doctype/email_template/email_template.py +++ b/frappe/email/doctype/email_template/email_template.py @@ -1,11 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json from frappe.model.document import Document from frappe.utils.jinja import validate_template -from six import string_types class EmailTemplate(Document): def validate(self): @@ -24,7 +22,7 @@ class EmailTemplate(Document): return frappe.render_template(self.response, doc) def get_formatted_email(self, doc): - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) return { @@ -36,7 +34,7 @@ class EmailTemplate(Document): @frappe.whitelist() def get_email_template(template_name, doc): '''Returns the processed HTML of a email template with the given doc''' - if isinstance(doc, string_types): + if isinstance(doc, str): doc = json.loads(doc) email_template = frappe.get_doc("Email Template", template_name) diff --git a/frappe/email/doctype/email_template/test_email_template.py b/frappe/email/doctype/email_template/test_email_template.py index a48ce94ac5..5a9ee969c6 100644 --- a/frappe/email/doctype/email_template/test_email_template.py +++ b/frappe/email/doctype/email_template/test_email_template.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestEmailTemplate(unittest.TestCase): diff --git a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py index e532e2b7eb..6c47d8c538 100644 --- a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py +++ b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ diff --git a/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py b/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py index ea84253ab6..602840fe3b 100644 --- a/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py +++ b/frappe/email/doctype/email_unsubscribe/test_email_unsubscribe.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 6412338e96..97d77549b7 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -1,14 +1,11 @@ # 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 import frappe.utils from frappe import throw, _ from frappe.website.website_generator import WebsiteGenerator from frappe.utils.verified_command import get_signed_params, verify_request -from frappe.email.queue import send from frappe.email.doctype.email_group.email_group import add_subscribers from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index bd8fadc29c..3abd339ed9 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import unittest from random import choice @@ -44,7 +42,7 @@ class TestNewsletter(unittest.TestCase): email_queue_list = [frappe.get_doc("Email Queue", e.name) for e in frappe.get_all("Email Queue")] self.assertEqual(len(email_queue_list), 4) - recipients = set([e.recipients[0].recipient for e in email_queue_list]) + recipients = {e.recipients[0].recipient for e in email_queue_list} self.assertTrue(set(emails).issubset(recipients)) def test_unsubscribe(self): diff --git a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py b/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py index a59ac372fd..a453dda9e4 100644 --- a/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py +++ b/frappe/email/doctype/newsletter_email_group/newsletter_email_group.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 2940a34f63..57418515f5 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import json, os from frappe import _ @@ -12,7 +11,6 @@ from frappe.utils import validate_email_address, nowdate, parse_val, is_html, ad from frappe.utils.jinja import validate_template from frappe.utils.safe_exec import get_safe_globals from frappe.modules.utils import export_module_json, get_doc_module -from six import string_types from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message from frappe.core.doctype.sms_settings.sms_settings import send_sms from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification @@ -55,9 +53,7 @@ class Notification(Document): # py if not os.path.exists(path + '.py'): with open(path + '.py', 'w') as f: - f.write("""from __future__ import unicode_literals - -import frappe + f.write("""import frappe def get_context(context): # do your magic here @@ -397,7 +393,7 @@ def trigger_notifications(doc, method=None): def evaluate_alert(doc, alert, event): from jinja2 import TemplateError try: - if isinstance(alert, string_types): + if isinstance(alert, str): alert = frappe.get_doc("Notification", alert) context = get_context(doc) diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index 31d5d9d1cc..d6358ccbbe 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe, frappe.utils, frappe.utils.scheduler from frappe.desk.form import assign_to import unittest diff --git a/frappe/email/doctype/notification_recipient/notification_recipient.py b/frappe/email/doctype/notification_recipient/notification_recipient.py index a85ed62c04..d8480c5455 100644 --- a/frappe/email/doctype/notification_recipient/notification_recipient.py +++ b/frappe/email/doctype/notification_recipient/notification_recipient.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/doctype/unhandled_email/test_unhandled_email.py b/frappe/email/doctype/unhandled_email/test_unhandled_email.py index 6cabcf6ec2..5606b8ff30 100644 --- a/frappe/email/doctype/unhandled_email/test_unhandled_email.py +++ b/frappe/email/doctype/unhandled_email/test_unhandled_email.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/email/doctype/unhandled_email/unhandled_email.py b/frappe/email/doctype/unhandled_email/unhandled_email.py index 1276da71a1..6414dbece3 100644 --- a/frappe/email/doctype/unhandled_email/unhandled_email.py +++ b/frappe/email/doctype/unhandled_email/unhandled_email.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 3b03c42b95..ffb44d3412 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -1,14 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, re, os from frappe.utils.pdf import get_pdf from frappe.email.doctype.email_account.email_account import EmailAccount from frappe.utils import (get_url, scrub_urls, strip, expand_relative_urls, cint, split_emails, to_markdown, markdown, random_string, parse_addr) import email.utils -from six import iteritems, text_type, string_types from email.mime.multipart import MIMEMultipart from email.header import Header from email import policy @@ -55,7 +53,7 @@ class EMail: from email import charset as Charset Charset.add_charset('utf-8', Charset.QP, Charset.QP, 'utf-8') - if isinstance(recipients, string_types): + if isinstance(recipients, str): recipients = recipients.replace(';', ',').replace('\n', '') recipients = split_emails(recipients) @@ -225,7 +223,7 @@ class EMail: } # reset headers as values may be changed. - for key, val in iteritems(headers): + for key, val in headers.items(): if val: self.set_header(key, val) @@ -328,7 +326,7 @@ def add_attachment(fname, fcontent, content_type=None, maintype, subtype = content_type.split('/', 1) if maintype == 'text': # Note: we should handle calculating the charset - if isinstance(fcontent, text_type): + if isinstance(fcontent, str): fcontent = fcontent.encode("utf-8") part = MIMEText(fcontent, _subtype=subtype, _charset="utf-8") elif maintype == 'image': @@ -345,7 +343,7 @@ def add_attachment(fname, fcontent, content_type=None, # Set the filename parameter if fname: attachment_type = 'inline' if inline else 'attachment' - part.add_header('Content-Disposition', attachment_type, filename=text_type(fname)) + part.add_header('Content-Disposition', attachment_type, filename=str(fname)) if content_id: part.add_header('Content-ID', '<{0}>'.format(content_id)) @@ -353,9 +351,7 @@ def add_attachment(fname, fcontent, content_type=None, def get_message_id(): '''Returns Message ID created from doctype and name''' - return "<{unique}@{site}>".format( - site=frappe.local.site, - unique=email.utils.make_msgid(random_string(10)).split('@')[0].split('<')[1]) + return email.utils.make_msgid(domain=frappe.local.site) def get_signature(email_account): if email_account and email_account.add_signature and email_account.signature: @@ -452,7 +448,7 @@ def get_header(header=None): if not header: return None - if isinstance(header, string_types): + if isinstance(header, str): # header = 'My Title' header = [header, None] if len(header) == 1: diff --git a/frappe/email/inbox.py b/frappe/email/inbox.py index 395a2d3e2d..c6020e14e4 100644 --- a/frappe/email/inbox.py +++ b/frappe/email/inbox.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe import json @@ -18,7 +18,7 @@ def get_email_accounts(user=None): "all_accounts": "" } - all_accounts = ",".join([ account.get("email_account") for account in accounts ]) + all_accounts = ",".join(account.get("email_account") for account in accounts) if len(accounts) > 1: email_accounts.append({ "email_account": all_accounts, diff --git a/frappe/email/queue.py b/frappe/email/queue.py index 52c91baf1c..885a306cfb 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -1,269 +1,66 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe -import sys -from six.moves import html_parser as HTMLParser -import smtplib, quopri, json -from frappe import msgprint, _, safe_decode, safe_encode, enqueue -from frappe.email.smtp import SMTPServer -from frappe.email.doctype.email_account.email_account import EmailAccount -from frappe.email.email_body import get_email, get_formatted_html, add_attachment +from frappe import msgprint, _ from frappe.utils.verified_command import get_signed_params, verify_request -from html2text import html2text -from frappe.utils import get_url, nowdate, now_datetime, add_days, split_emails, cstr, cint -from rq.timeouts import JobTimeoutException -from six import text_type, string_types, PY3 -from email.parser import Parser +from frappe.utils import get_url, now_datetime, cint +def get_emails_sent_this_month(email_account=None): + """Get count of emails sent from a specific email account. -class EmailLimitCrossedError(frappe.ValidationError): pass + :param email_account: name of the email account used to send mail -def send(recipients=None, sender=None, subject=None, message=None, text_content=None, reference_doctype=None, - reference_name=None, unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None, - attachments=None, reply_to=None, cc=None, bcc=None, message_id=None, in_reply_to=None, send_after=None, - expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, - queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None, - header=None, print_letterhead=False, with_container=False): - """Add email to sending queue (Email Queue) - - :param recipients: List of recipients. - :param sender: Email sender. - :param subject: Email subject. - :param message: Email message. - :param text_content: Text version of email message. - :param reference_doctype: Reference DocType of caller document. - :param reference_name: Reference name of caller document. - :param send_priority: Priority for Email Queue, default 1. - :param unsubscribe_method: URL method for unsubscribe. Default is `/api/method/frappe.email.queue.unsubscribe`. - :param unsubscribe_params: additional params for unsubscribed links. default are name, doctype, email - :param attachments: Attachments to be sent. - :param reply_to: Reply to be captured here (default inbox) - :param in_reply_to: Used to send the Message-Id of a received email back as In-Reply-To. - :param send_after: Send this email after the given datetime. If value is in integer, then `send_after` will be the automatically set to no of days from current date. - :param communication: Communication link to be set in Email Queue record - :param now: Send immediately (don't send in the background) - :param queue_separately: Queue each email separately - :param is_notification: Marks email as notification so will not trigger notifications from system - :param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. - :param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id - :param header: Append header in email (boolean) - :param with_container: Wraps email inside styled container + if email_account=None, email account filter is not applied while counting """ - if not unsubscribe_method: - unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" - - if not recipients and not cc: - return - - if not cc: - cc = [] - if not bcc: - bcc = [] - - if isinstance(recipients, string_types): - recipients = split_emails(recipients) - - if isinstance(cc, string_types): - cc = split_emails(cc) - - if isinstance(bcc, string_types): - bcc = split_emails(bcc) - - if isinstance(send_after, int): - send_after = add_days(nowdate(), send_after) - - email_account = EmailAccount.find_outgoing( - match_by_doctype=reference_doctype, match_by_email=sender, _raise_error=True) - - if not sender or sender == "Administrator": - sender = email_account.default_sender - - if not text_content: - try: - text_content = html2text(message) - except HTMLParser.HTMLParseError: - text_content = "See html attachment" - - recipients = list(set(recipients)) - cc = list(set(cc)) - - all_ids = tuple(recipients + cc) - - unsubscribed = frappe.db.sql_list(''' + q = """ SELECT - distinct email - from - `tabEmail Unsubscribe` - where - email in %(all_ids)s - and ( - ( - reference_doctype = %(reference_doctype)s - and reference_name = %(reference_name)s - ) - or global_unsubscribe = 1 - ) - ''', { - 'all_ids': all_ids, - 'reference_doctype': reference_doctype, - 'reference_name': reference_name, - }) + COUNT(*) + FROM + `tabEmail Queue` + WHERE + `status`='Sent' + AND + EXTRACT(YEAR_MONTH FROM `creation`) = EXTRACT(YEAR_MONTH FROM NOW()) + """ - recipients = [r for r in recipients if r and r not in unsubscribed] + q_args = {} + if email_account is not None: + if email_account: + q += " AND email_account = %(email_account)s" + q_args['email_account'] = email_account + else: + q += " AND (email_account is null OR email_account='')" - if cc: - cc = [r for r in cc if r and r not in unsubscribed] + return frappe.db.sql(q, q_args)[0][0] - if not recipients and not cc: - # Recipients may have been unsubscribed, exit quietly - return +def get_emails_sent_today(email_account=None): + """Get count of emails sent from a specific email account. - email_text_context = text_content + :param email_account: name of the email account used to send mail - should_append_unsubscribe = (add_unsubscribe_link - and reference_doctype - and (unsubscribe_message or reference_doctype=="Newsletter") - and add_unsubscribe_link==1) + if email_account=None, email account filter is not applied while counting + """ + q = """ + SELECT + COUNT(`name`) + FROM + `tabEmail Queue` + WHERE + `status` in ('Sent', 'Not Sent', 'Sending') + AND + `creation` > (NOW() - INTERVAL '24' HOUR) + """ - unsubscribe_link = None - if should_append_unsubscribe: - unsubscribe_link = get_unsubscribe_message(unsubscribe_message, expose_recipients) - email_text_context += unsubscribe_link.text + q_args = {} + if email_account is not None: + if email_account: + q += " AND email_account = %(email_account)s" + q_args['email_account'] = email_account + else: + q += " AND (email_account is null OR email_account='')" - email_content = get_formatted_html(subject, message, - email_account=email_account, header=header, - unsubscribe_link=unsubscribe_link, with_container=with_container) - - # add to queue - add(recipients, sender, subject, - formatted=email_content, - text_content=email_text_context, - reference_doctype=reference_doctype, - reference_name=reference_name, - attachments=attachments, - reply_to=reply_to, - cc=cc, - bcc=bcc, - message_id=message_id, - in_reply_to=in_reply_to, - send_after=send_after, - send_priority=send_priority, - email_account=email_account, - communication=communication, - add_unsubscribe_link=add_unsubscribe_link, - unsubscribe_method=unsubscribe_method, - unsubscribe_params=unsubscribe_params, - expose_recipients=expose_recipients, - read_receipt=read_receipt, - queue_separately=queue_separately, - is_notification = is_notification, - inline_images = inline_images, - header=header, - now=now, - print_letterhead=print_letterhead) - - -def add(recipients, sender, subject, **kwargs): - """Add to Email Queue""" - if kwargs.get('queue_separately') or len(recipients) > 20: - email_queue = None - for r in recipients: - if not email_queue: - email_queue = get_email_queue([r], sender, subject, **kwargs) - if kwargs.get('now'): - email_queue.send() - else: - duplicate = email_queue.get_duplicate([r]) - duplicate.insert(ignore_permissions=True) - - if kwargs.get('now'): - duplicate.send() - - frappe.db.commit() - else: - email_queue = get_email_queue(recipients, sender, subject, **kwargs) - if kwargs.get('now'): - email_queue.send() - -def get_email_queue(recipients, sender, subject, **kwargs): - '''Make Email Queue object''' - e = frappe.new_doc('Email Queue') - e.priority = kwargs.get('send_priority') - attachments = kwargs.get('attachments') - if attachments: - # store attachments with fid or print format details, to be attached on-demand later - _attachments = [] - for att in attachments: - if att.get('fid'): - _attachments.append(att) - elif att.get("print_format_attachment") == 1: - if not att.get('lang', None): - att['lang'] = frappe.local.lang - att['print_letterhead'] = kwargs.get('print_letterhead') - _attachments.append(att) - e.attachments = json.dumps(_attachments) - - try: - mail = get_email(recipients, - sender=sender, - subject=subject, - formatted=kwargs.get('formatted'), - text_content=kwargs.get('text_content'), - attachments=kwargs.get('attachments'), - reply_to=kwargs.get('reply_to'), - cc=kwargs.get('cc'), - bcc=kwargs.get('bcc'), - email_account=kwargs.get('email_account'), - expose_recipients=kwargs.get('expose_recipients'), - inline_images=kwargs.get('inline_images'), - header=kwargs.get('header')) - - mail.set_message_id(kwargs.get('message_id'),kwargs.get('is_notification')) - if kwargs.get('read_receipt'): - mail.msg_root["Disposition-Notification-To"] = sender - if kwargs.get('in_reply_to'): - mail.set_in_reply_to(kwargs.get('in_reply_to')) - - e.message_id = mail.msg_root["Message-Id"].strip(" <>") - e.message = cstr(mail.as_string()) - e.sender = mail.sender - - except frappe.InvalidEmailAddressError: - # bad Email Address - don't add to queue - import traceback - frappe.log_error('Invalid Email ID Sender: {0}, Recipients: {1}, \nTraceback: {2} '.format(mail.sender, - ', '.join(mail.recipients), traceback.format_exc()), 'Email Not Sent') - - recipients = list(set(recipients + kwargs.get('cc', []) + kwargs.get('bcc', []))) - email_account = kwargs.get('email_account') - email_account_name = email_account and email_account.is_exists_in_db() and email_account.name - - e.set_recipients(recipients) - e.reference_doctype = kwargs.get('reference_doctype') - e.reference_name = kwargs.get('reference_name') - e.add_unsubscribe_link = kwargs.get("add_unsubscribe_link") - e.unsubscribe_method = kwargs.get('unsubscribe_method') - e.unsubscribe_params = kwargs.get('unsubscribe_params') - e.expose_recipients = kwargs.get('expose_recipients') - e.communication = kwargs.get('communication') - e.send_after = kwargs.get('send_after') - e.show_as_cc = ",".join(kwargs.get('cc', [])) - e.show_as_bcc = ",".join(kwargs.get('bcc', [])) - e.email_account = email_account_name or None - e.insert(ignore_permissions=True) - return e - -def get_emails_sent_this_month(): - return frappe.db.sql(""" - SELECT COUNT(*) FROM `tabEmail Queue` - WHERE `status`='Sent' AND EXTRACT(YEAR_MONTH FROM `creation`) = EXTRACT(YEAR_MONTH FROM NOW()) - """)[0][0] - -def get_emails_sent_today(): - return frappe.db.sql("""SELECT COUNT(`name`) FROM `tabEmail Queue` WHERE - `status` in ('Sent', 'Not Sent', 'Sending') AND `creation` > (NOW() - INTERVAL '24' HOUR)""")[0][0] + return frappe.db.sql(q, q_args)[0][0] def get_unsubscribe_message(unsubscribe_message, expose_recipients): if unsubscribe_message: diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 6d60007cdb..2e42008951 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -8,11 +8,11 @@ import imaplib import poplib import re import time +import json from email.header import decode_header import _socket import chardet -import six from email_reply_parser import EmailReplyParser import frappe @@ -20,13 +20,26 @@ from frappe import _, safe_decode, safe_encode from frappe.core.doctype.file.file import (MaxFileSizeReachedError, get_random_filename) from frappe.utils import (cint, convert_utc_to_user_timezone, cstr, - extract_email_id, markdown, now, parse_addr, strip) + extract_email_id, markdown, now, parse_addr, strip, get_datetime, + add_days, sanitize_html) +from frappe.utils.user import is_system_user +from frappe.utils.html_utils import clean_email_html + +# fix due to a python bug in poplib that limits it to 2048 +poplib._MAXLINE = 20480 +imaplib._MAXLINE = 20480 + +# fix due to a python bug in poplib that limits it to 2048 +poplib._MAXLINE = 20480 +imaplib._MAXLINE = 20480 class EmailSizeExceededError(frappe.ValidationError): pass class EmailTimeoutError(frappe.ValidationError): pass class TotalSizeExceededError(frappe.ValidationError): pass class LoginLimitExceeded(frappe.ValidationError): pass +class SentEmailInInboxError(Exception): + pass class EmailServer: """Wrapper for POP server to pull emails.""" @@ -100,14 +113,11 @@ class EmailServer: def get_messages(self): """Returns new email messages in a list.""" - if not self.check_mails(): - return # nothing to do + if not (self.check_mails() or self.connect()): + return [] frappe.db.commit() - if not self.connect(): - return - uid_list = [] try: @@ -116,7 +126,6 @@ class EmailServer: self.latest_messages = [] self.seen_status = {} self.uid_reindexed = False - uid_list = email_list = self.get_new_mails() if not email_list: @@ -132,11 +141,7 @@ class EmailServer: self.max_email_size = cint(frappe.local.conf.get("max_email_size")) self.max_total_size = 5 * self.max_email_size - for i, message_meta in enumerate(email_list): - # do not pull more than NUM emails - if (i+1) > num: - break - + for i, message_meta in enumerate(email_list[:num]): try: self.retrieve_message(message_meta, i+1) except (TotalSizeExceededError, EmailTimeoutError, LoginLimitExceeded): @@ -152,7 +157,6 @@ class EmailServer: except Exception as e: if self.has_login_limit_exceeded(e): pass - else: raise @@ -361,14 +365,12 @@ class Email: """Parses headers, content, attachments from given raw message. :param content: Raw message.""" - if six.PY2: - self.mail = email.message_from_string(safe_encode(content)) + if isinstance(content, bytes): + self.mail = email.message_from_bytes(content) else: - if isinstance(content, bytes): - self.mail = email.message_from_bytes(content) - else: - self.mail = email.message_from_string(content) + self.mail = email.message_from_string(content) + self.raw_message = content self.text_content = '' self.html_content = '' self.attachments = [] @@ -391,6 +393,10 @@ class Email: if self.date > now(): self.date = now() + @property + def in_reply_to(self): + return (self.mail.get("In-Reply-To") or "").strip(" <>") + def parse(self): """Walk and process multi-part email.""" for part in self.mail.walk(): @@ -558,10 +564,327 @@ class Email: l = re.findall(r'(?<=\[)[\w/-]+', self.subject) return l and l[0] or None + def is_reply(self): + return bool(self.in_reply_to) -# fix due to a python bug in poplib that limits it to 2048 -poplib._MAXLINE = 20480 -imaplib._MAXLINE = 20480 +class InboundMail(Email): + """Class representation of incoming mail along with mail handlers. + """ + def __init__(self, content, email_account, uid=None, seen_status=None): + super().__init__(content) + self.email_account = email_account + self.uid = uid or -1 + self.seen_status = seen_status or 0 + + # System documents related to this mail + self._parent_email_queue = None + self._parent_communication = None + self._reference_document = None + + self.flags = frappe._dict() + + def get_content(self): + if self.content_type == 'text/html': + return clean_email_html(self.content) + + def process(self): + """Create communication record from email. + """ + if self.is_sender_same_as_receiver() and not self.is_reply(): + if frappe.flags.in_test: + print('WARN: Cannot pull email. Sender same as recipient inbox') + raise SentEmailInInboxError + + communication = self.is_exist_in_system() + if communication: + communication.update_db(uid=self.uid) + communication.reload() + return communication + + self.flags.is_new_communication = True + return self._build_communication_doc() + + def _build_communication_doc(self): + data = self.as_dict() + data['doctype'] = "Communication" + + if self.parent_communication(): + data['in_reply_to'] = self.parent_communication().name + + if self.reference_document(): + data['reference_doctype'] = self.reference_document().doctype + data['reference_name'] = self.reference_document().name + elif self.email_account.append_to and self.email_account.append_to != 'Communication': + reference_doc = self._create_reference_document(self.email_account.append_to) + if reference_doc: + data['reference_doctype'] = reference_doc.doctype + data['reference_name'] = reference_doc.name + data['is_first'] = True + + if self.is_notification(): + # Disable notifications for notification. + data['unread_notification_sent'] = 1 + + if self.seen_status: + data['_seen'] = json.dumps(self.get_users_linked_to_account(self.email_account)) + + communication = frappe.get_doc(data) + communication.flags.in_receive = True + communication.insert(ignore_permissions=True) + + # save attachments + communication._attachments = self.save_attachments_in_doc(communication) + communication.content = sanitize_html(self.replace_inline_images(communication._attachments)) + communication.save() + return communication + + def replace_inline_images(self, attachments): + # replace inline images + content = self.content + for file in attachments: + if file.name in self.cid_map and self.cid_map[file.name]: + content = content.replace("cid:{0}".format(self.cid_map[file.name]), + file.file_url) + return content + + def is_notification(self): + isnotification = self.mail.get("isnotification") + return isnotification and ("notification" in isnotification) + + def is_exist_in_system(self): + """Check if this email already exists in the system(as communication document). + """ + from frappe.core.doctype.communication.communication import Communication + if not self.message_id: + return + + return Communication.find_one_by_filters(message_id = self.message_id, + order_by = 'creation DESC') + + def is_sender_same_as_receiver(self): + return self.from_email == self.email_account.email_id + + def is_reply_to_system_sent_mail(self): + """Is it a reply to already sent mail. + """ + return self.is_reply() and frappe.local.site in self.in_reply_to + + def parent_email_queue(self): + """Get parent record from `Email Queue`. + + If it is a reply to already sent mail, then there will be a parent record in EMail Queue. + """ + from frappe.email.doctype.email_queue.email_queue import EmailQueue + + if self._parent_email_queue is not None: + return self._parent_email_queue + + parent_email_queue = '' + if self.is_reply_to_system_sent_mail(): + parent_email_queue = EmailQueue.find_one_by_filters(message_id=self.in_reply_to) + + self._parent_email_queue = parent_email_queue or '' + return self._parent_email_queue + + def parent_communication(self): + """Find a related communication so that we can prepare a mail thread. + + The way it happens is by using in-reply-to header, and we can't make thread if it does not exist. + + Here are the cases to handle: + 1. If mail is a reply to already sent mail, then we can get parent communicaion from + Email Queue record. + 2. Sometimes we send communication name in message-ID directly, use that to get parent communication. + 3. Sender sent a reply but reply is on top of what (s)he sent before, + then parent record exists directly in communication. + """ + from frappe.core.doctype.communication.communication import Communication + if self._parent_communication is not None: + return self._parent_communication + + if not self.is_reply(): + return '' + + if not self.is_reply_to_system_sent_mail(): + communication = Communication.find_one_by_filters(message_id=self.in_reply_to, + creation = ['>=', self.get_relative_dt(-30)]) + elif self.parent_email_queue() and self.parent_email_queue().communication: + communication = Communication.find(self.parent_email_queue().communication, ignore_error=True) + else: + reference = self.in_reply_to + if '@' in self.in_reply_to: + reference, _ = self.in_reply_to.split("@", 1) + communication = Communication.find(reference, ignore_error=True) + + self._parent_communication = communication or '' + return self._parent_communication + + def reference_document(self): + """Reference document is a document to which mail relate to. + + We can get reference document from Parent record(EmailQueue | Communication) if exists. + Otherwise we do subject match to find reference document if we know the reference(append_to) doctype. + """ + if self._reference_document is not None: + return self._reference_document + + reference_document = "" + parent = self.parent_email_queue() or self.parent_communication() + + if parent and parent.reference_doctype: + reference_doctype, reference_name = parent.reference_doctype, parent.reference_name + reference_document = self.get_doc(reference_doctype, reference_name, ignore_error=True) + + if not reference_document and self.email_account.append_to: + reference_document = self.match_record_by_subject_and_sender(self.email_account.append_to) + + self._reference_document = reference_document or '' + return self._reference_document + + def get_reference_name_from_subject(self): + """ + Ex: "Re: Your email (#OPP-2020-2334343)" + """ + return self.subject.rsplit('#', 1)[-1].strip(' ()') + + def match_record_by_subject_and_sender(self, doctype): + """Find a record in the given doctype that matches with email subject and sender. + + Cases: + 1. Sometimes record name is part of subject. We can get document by parsing name from subject + 2. Find by matching sender and subject + 3. Find by matching subject alone (Special case) + Ex: when a System User is using Outlook and replies to an email from their own client, + it reaches the Email Account with the threading info lost and the (sender + subject match) + doesn't work because the sender in the first communication was someone different to whom + the system user is replying to via the common email account in Frappe. This fix bypasses + the sender match when the sender is a system user and subject is atleast 10 chars long + (for additional safety) + + NOTE: We consider not to match by subject if match record is very old. + """ + name = self.get_reference_name_from_subject() + email_fields = self.get_email_fields(doctype) + + record = self.get_doc(doctype, name, ignore_error=True) if name else None + + if not record: + subject = self.clean_subject(self.subject) + filters = { + email_fields.subject_field: ("like", f"%{subject}%"), + "creation": (">", self.get_relative_dt(days=-60)) + } + + # Sender check is not needed incase mail is from system user. + if not (len(subject) > 10 and is_system_user(self.from_email)): + filters[email_fields.sender_field] = self.from_email + + name = frappe.db.get_value(self.email_account.append_to, filters = filters) + record = self.get_doc(doctype, name, ignore_error=True) if name else None + return record + + def _create_reference_document(self, doctype): + """ Create reference document if it does not exist in the system. + """ + parent = frappe.new_doc(doctype) + email_fileds = self.get_email_fields(doctype) + + if email_fileds.subject_field: + parent.set(email_fileds.subject_field, frappe.as_unicode(self.subject)[:140]) + + if email_fileds.sender_field: + parent.set(email_fileds.sender_field, frappe.as_unicode(self.from_email)) + + parent.flags.ignore_mandatory = True + + try: + parent.insert(ignore_permissions=True) + except frappe.DuplicateEntryError: + # try and find matching parent + parent_name = frappe.db.get_value(self.email_account.append_to, + {email_fileds.sender_field: self.from_email} + ) + if parent_name: + parent.name = parent_name + else: + parent = None + return parent + + + @staticmethod + def get_doc(doctype, docname, ignore_error=False): + try: + return frappe.get_doc(doctype, docname) + except frappe.DoesNotExistError: + if ignore_error: + return + raise + + @staticmethod + def get_relative_dt(days): + """Get relative to current datetime. Only relative days are supported. + """ + return add_days(get_datetime(), days) + + @staticmethod + def get_users_linked_to_account(email_account): + """Get list of users who linked to Email account. + """ + users = frappe.get_all("User Email", filters={"email_account": email_account.name}, + fields=["parent"]) + return list(set([user.get("parent") for user in users])) + + @staticmethod + def clean_subject(subject): + """Remove Prefixes like 'fw', FWD', 're' etc from subject. + """ + # Match strings like "fw:", "re :" etc. + regex = r"(^\s*(fw|fwd|wg)[^:]*:|\s*(re|aw)[^:]*:\s*)*" + return frappe.as_unicode(strip(re.sub(regex, "", subject, 0, flags=re.IGNORECASE))) + + @staticmethod + def get_email_fields(doctype): + """Returns Email related fields of a doctype. + """ + fields = frappe._dict() + + email_fields = ['subject_field', 'sender_field'] + meta = frappe.get_meta(doctype) + + for field in email_fields: + if hasattr(meta, field): + fields[field] = getattr(meta, field) + return fields + + @staticmethod + def get_document(self, doctype, name): + """Is same as frappe.get_doc but suppresses the DoesNotExist error. + """ + try: + return frappe.get_doc(doctype, name) + except frappe.DoesNotExistError: + return None + + def as_dict(self): + """ + """ + return { + "subject": self.subject, + "content": self.get_content(), + 'text_content': self.text_content, + "sent_or_received": "Received", + "sender_full_name": self.from_real_name, + "sender": self.from_email, + "recipients": self.mail.get("To"), + "cc": self.mail.get("CC"), + "email_account": self.email_account.name, + "communication_medium": "Email", + "uid": self.uid, + "message_id": self.message_id, + "communication_date": self.date, + "has_attachment": 1 if self.attachments else 0, + "seen": self.seen_status or 0 + } class TimerMixin(object): def __init__(self, *args, **kwargs): diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 3acb76af23..74492c09c3 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import smtplib import email.utils @@ -85,18 +84,19 @@ class SMTPServer: SMTP = smtplib.SMTP_SSL if self.use_ssl else smtplib.SMTP try: - self._session = SMTP(self.server, self.port) - if not self._session: + _session = SMTP(self.server, self.port) + if not _session: frappe.msgprint(CONNECTION_FAILED, raise_exception=frappe.OutgoingEmailError) - self.secure_session(self._session) + self.secure_session(_session) if self.login and self.password: - res = self._session.login(str(self.login or ""), str(self.password or "")) + res = _session.login(str(self.login or ""), str(self.password or "")) # check if logged correctly if res[0]!=235: frappe.msgprint(res[1], raise_exception=frappe.OutgoingEmailError) + self._session = _session return self._session except smtplib.SMTPAuthenticationError as e: diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py index 33668cddba..8e637273ed 100644 --- a/frappe/email/test_email_body.py +++ b/frappe/email/test_email_body.py @@ -1,15 +1,12 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - import unittest, os, base64 from frappe import safe_decode from frappe.email.receive import Email from frappe.email.email_body import (replace_filename_with_cid, get_email, inline_style_in_html, get_header) -from frappe.email.queue import get_email_queue -from frappe.email.doctype.email_queue.email_queue import SendMailContext -from six import PY3 +from frappe.email.doctype.email_queue.email_queue import SendMailContext, QueueBuilder + class TestEmailBody(unittest.TestCase): def setUp(self): @@ -42,41 +39,31 @@ This is the text version of this email ).as_string().replace("\r\n", "\n") def test_prepare_message_returns_already_encoded_string(self): + uni_chr1 = chr(40960) + uni_chr2 = chr(1972) - if PY3: - uni_chr1 = chr(40960) - uni_chr2 = chr(1972) - else: - uni_chr1 = unichr(40960) - uni_chr2 = unichr(1972) - - email = get_email_queue( + queue_doc = QueueBuilder( recipients=['test@example.com'], sender='me@example.com', subject='Test Subject', - content='

' + uni_chr1 + 'abcd' + uni_chr2 + '

', - formatted='

' + uni_chr1 + 'abcd' + uni_chr2 + '

', - text_content='whatever') - mail_ctx = SendMailContext(queue_doc = email) + message='

' + uni_chr1 + 'abcd' + uni_chr2 + '

', + text_content='whatever').process()[0] + mail_ctx = SendMailContext(queue_doc = queue_doc) result = mail_ctx.build_message(recipient_email = 'test@test.com') self.assertTrue(b"

=EA=80=80abcd=DE=B4

" in result) def test_prepare_message_returns_cr_lf(self): - email = get_email_queue( + queue_doc = QueueBuilder( recipients=['test@example.com'], sender='me@example.com', subject='Test Subject', - content='

\n this is a test of newlines\n' + '

', - formatted='

\n this is a test of newlines\n' + '

', - text_content='whatever') + message='

\n this is a test of newlines\n' + '

', + text_content='whatever').process()[0] - mail_ctx = SendMailContext(queue_doc = email) + mail_ctx = SendMailContext(queue_doc = queue_doc) result = safe_decode(mail_ctx.build_message(recipient_email='test@test.com')) - if PY3: - self.assertTrue(result.count('\n') == result.count("\r")) - else: - self.assertTrue(True) + self.assertTrue(result.count('\n') == result.count("\r")) def test_image(self): img_signature = ''' diff --git a/frappe/email/utils.py b/frappe/email/utils.py index 8b4bd95ba0..24ce77b922 100644 --- a/frappe/email/utils.py +++ b/frappe/email/utils.py @@ -1,7 +1,5 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -from __future__ import unicode_literals, print_function import imaplib, poplib from frappe.utils import cint diff --git a/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py b/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py index 1ab9534bdc..fc8164d8a4 100644 --- a/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py +++ b/frappe/event_streaming/doctype/document_type_field_mapping/document_type_field_mapping.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py b/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py index bf96e4e27b..2cf7282a5a 100644 --- a/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py +++ b/frappe/event_streaming/doctype/document_type_mapping/document_type_mapping.py @@ -1,12 +1,9 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt - -from __future__ import unicode_literals import frappe import json from frappe import _ -from six import iteritems from frappe.model.document import Document from frappe.model import default_fields @@ -100,7 +97,7 @@ class DocumentTypeMapping(Document): def get_mapped_dependency(self, mapping, producer_site, doc): inner_mapping = frappe.get_doc('Document Type Mapping', mapping.mapping) filters = json.loads(mapping.remote_value_filters) - for key, value in iteritems(filters): + for key, value in filters.items(): if value.startswith('eval:'): val = frappe.safe_eval(value[5:], None, dict(doc=doc)) filters[key] = val @@ -117,7 +114,7 @@ class DocumentTypeMapping(Document): def map_rows_removed(self, update_diff, mapping): removed = [] mapping['removed'] = update_diff.removed - for key, value in iteritems(update_diff.removed.copy()): + for key, value in update_diff.removed.copy().items(): local_table_name = frappe.db.get_value('Document Type Field Mapping', { 'remote_fieldname': key, 'parent': self.name @@ -133,7 +130,7 @@ class DocumentTypeMapping(Document): def map_rows(self, update_diff, mapping, producer_site, operation): remote_fields = [] - for tablename, entries in iteritems(update_diff.get(operation).copy()): + for tablename, entries in update_diff.get(operation).copy().items(): local_table_name = frappe.db.get_value('Document Type Field Mapping', {'remote_fieldname': tablename}, 'local_fieldname') table_map = frappe.db.get_value('Document Type Field Mapping', {'local_fieldname': local_table_name, 'parent': self.name}, 'mapping') table_map = frappe.get_doc('Document Type Mapping', table_map) diff --git a/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py b/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py index 178d7b6b6a..b1bb322855 100644 --- a/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py +++ b/frappe/event_streaming/doctype/document_type_mapping/test_document_type_mapping.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_consumer/event_consumer.py b/frappe/event_streaming/doctype/event_consumer/event_consumer.py index 5789e09e74..00d304f7f4 100644 --- a/frappe/event_streaming/doctype/event_consumer/event_consumer.py +++ b/frappe/event_streaming/doctype/event_consumer/event_consumer.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import json import requests @@ -31,7 +30,7 @@ class EventConsumer(Document): self.update_consumer_status() else: frappe.db.set_value(self.doctype, self.name, 'incoming_change', 0) - + frappe.cache().delete_value('event_consumer_document_type_map') def on_trash(self): diff --git a/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py b/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py index 9e344842bd..b8072ecabd 100644 --- a/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py +++ b/frappe/event_streaming/doctype/event_consumer/test_event_consumer.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py b/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py index 197338027f..cf5d18edfd 100644 --- a/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py +++ b/frappe/event_streaming/doctype/event_consumer_document_type/event_consumer_document_type.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_producer/test_event_producer.py b/frappe/event_streaming/doctype/event_producer/test_event_producer.py index 4c259c3729..883f4f2df2 100644 --- a/frappe/event_streaming/doctype/event_producer/test_event_producer.py +++ b/frappe/event_streaming/doctype/event_producer/test_event_producer.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest import json @@ -154,7 +152,7 @@ class TestEventProducer(unittest.TestCase): def test_conditional_events(self): producer = get_remote_site() - + # Add Condition event_producer = frappe.get_doc('Event Producer', producer_url) note_producer_entry = [ @@ -192,7 +190,7 @@ class TestEventProducer(unittest.TestCase): def test_conditional_events_with_cmd(self): producer = get_remote_site() - + # Add Condition event_producer = frappe.get_doc('Event Producer', producer_url) note_producer_entry = [ diff --git a/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py b/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py index 2870d5330f..9ae70e0f97 100644 --- a/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py +++ b/frappe/event_streaming/doctype/event_producer_document_type/event_producer_document_type.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py b/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py index 02e297bdd5..391cf79c27 100644 --- a/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py +++ b/frappe/event_streaming/doctype/event_producer_last_update/event_producer_last_update.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py b/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py index 0311cb2df9..62ea71edab 100644 --- a/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py +++ b/frappe/event_streaming/doctype/event_producer_last_update/test_event_producer_last_update.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py b/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py index 31b1f863aa..1d255a5c30 100644 --- a/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py +++ b/frappe/event_streaming/doctype/event_sync_log/event_sync_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py b/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py index 6c621b8b0e..ef55dc0f16 100644 --- a/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py +++ b/frappe/event_streaming/doctype/event_sync_log/test_event_sync_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_update_log/event_update_log.py b/frappe/event_streaming/doctype/event_update_log/event_update_log.py index 1c31718c2b..ae851c70d1 100644 --- a/frappe/event_streaming/doctype/event_update_log/event_update_log.py +++ b/frappe/event_streaming/doctype/event_update_log/event_update_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils.background_jobs import get_jobs @@ -235,7 +234,7 @@ def get_update_logs_for_consumer(event_consumer, doctypes, last_update): if isinstance(doctypes, str): doctypes = frappe.parse_json(doctypes) - + from frappe.event_streaming.doctype.event_consumer.event_consumer import has_consumer_access consumer = frappe.get_doc('Event Consumer', event_consumer) diff --git a/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py b/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py index e00fc767d9..99ced3c209 100644 --- a/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py +++ b/frappe/event_streaming/doctype/event_update_log/test_event_update_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py b/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py index ee6d5d8ca9..80a59e4c31 100644 --- a/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py +++ b/frappe/event_streaming/doctype/event_update_log_consumer/event_update_log_consumer.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/exceptions.py b/frappe/exceptions.py index ab65e6e006..13abd8f4f8 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -1,18 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals -import sys - # BEWARE don't put anything in this file except exceptions from werkzeug.exceptions import NotFound - -if sys.version_info.major == 2: - class FileNotFoundError(Exception): pass -else: - from builtins import FileNotFoundError - class SiteNotSpecifiedError(Exception): def __init__(self, *args, **kwargs): self.message = "Please specify --site sitename" diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index 054a8c9369..e57f82b60a 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -1,8 +1,6 @@ -from __future__ import print_function, unicode_literals import requests import json import frappe -from six import iteritems, string_types import base64 ''' @@ -88,7 +86,7 @@ class FrappeClient(object): def get_list(self, doctype, fields='["name"]', filters=None, limit_start=0, limit_page_length=0): """Returns list of records of a particular type""" - if not isinstance(fields, string_types): + if not isinstance(fields, str): fields = json.dumps(fields) params = { "fields": fields, @@ -310,7 +308,7 @@ class FrappeClient(object): def preprocess(self, params): """convert dicts, lists to json""" - for key, value in iteritems(params): + for key, value in params.items(): if isinstance(value, (dict, list)): params[key] = json.dumps(value) diff --git a/frappe/geo/country_info.py b/frappe/geo/country_info.py index 4f878325ad..ddebd1fb0e 100644 --- a/frappe/geo/country_info.py +++ b/frappe/geo/country_info.py @@ -2,8 +2,6 @@ # MIT License. See license.txt # all country info -from __future__ import unicode_literals - import os, json, frappe from frappe.utils.momentjs import get_all_timezones diff --git a/frappe/geo/doctype/country/__init__.py b/frappe/geo/doctype/country/__init__.py index baffc48825..8b13789179 100644 --- a/frappe/geo/doctype/country/__init__.py +++ b/frappe/geo/doctype/country/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals + diff --git a/frappe/geo/doctype/country/country.py b/frappe/geo/doctype/country/country.py index 5f8b6f7bd5..54935e6eaf 100644 --- a/frappe/geo/doctype/country/country.py +++ b/frappe/geo/doctype/country/country.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/geo/doctype/country/test_country.py b/frappe/geo/doctype/country/test_country.py index 81849d6886..e00d6ecf37 100644 --- a/frappe/geo/doctype/country/test_country.py +++ b/frappe/geo/doctype/country/test_country.py @@ -1,6 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Country') \ No newline at end of file diff --git a/frappe/geo/doctype/currency/__init__.py b/frappe/geo/doctype/currency/__init__.py index baffc48825..8b13789179 100644 --- a/frappe/geo/doctype/currency/__init__.py +++ b/frappe/geo/doctype/currency/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals + diff --git a/frappe/geo/doctype/currency/currency.py b/frappe/geo/doctype/currency/currency.py index 688303fd50..b3ce67cc67 100644 --- a/frappe/geo/doctype/currency/currency.py +++ b/frappe/geo/doctype/currency/currency.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: See license.txt -from __future__ import unicode_literals import frappe from frappe import throw, _ diff --git a/frappe/geo/doctype/currency/test_currency.py b/frappe/geo/doctype/currency/test_currency.py index 7945e193da..5552e675ec 100644 --- a/frappe/geo/doctype/currency/test_currency.py +++ b/frappe/geo/doctype/currency/test_currency.py @@ -3,6 +3,5 @@ # pre loaded -from __future__ import unicode_literals import frappe test_records = frappe.get_test_records('Currency') \ No newline at end of file diff --git a/frappe/geo/utils.py b/frappe/geo/utils.py index d94a13ea41..89de176f0b 100644 --- a/frappe/geo/utils.py +++ b/frappe/geo/utils.py @@ -2,8 +2,6 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe from pymysql import InternalError diff --git a/frappe/handler.py b/frappe/handler.py index b622667e18..de86c15c8f 100755 --- a/frappe/handler.py +++ b/frappe/handler.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - from werkzeug.wrappers import Response import frappe diff --git a/frappe/hooks.py b/frappe/hooks.py index d0968ce051..ac42a03461 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + from . import __version__ as app_version diff --git a/frappe/installer.py b/frappe/installer.py index d7d885d60e..d4d8117fcb 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -282,10 +282,10 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) def post_install(rebuild_website=False): - from frappe.website import render + from frappe.website.utils import clear_website_cache if rebuild_website: - render.clear_cache() + clear_website_cache() init_singles() frappe.db.commit() @@ -537,7 +537,7 @@ def is_downgrade(sql_file_path, verbose=False): def is_partial(sql_file_path): with open(sql_file_path) as f: - header = " ".join([f.readline() for _ in range(5)]) + header = " ".join(f.readline() for _ in range(5)) if "Partial Backup" in header: return True return False diff --git a/frappe/integrations/doctype/braintree_settings/braintree_settings.py b/frappe/integrations/doctype/braintree_settings/braintree_settings.py index 768f58c0a0..9dc9778bee 100644 --- a/frappe/integrations/doctype/braintree_settings/braintree_settings.py +++ b/frappe/integrations/doctype/braintree_settings/braintree_settings.py @@ -2,12 +2,11 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document import braintree from frappe import _ -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode from frappe.utils import get_url, call_hook_method from frappe.integrations.utils import create_request_log, create_payment_gateway diff --git a/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py b/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py index 80fa3c54b8..72a678a92c 100644 --- a/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py +++ b/frappe/integrations/doctype/braintree_settings/test_braintree_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestBraintreeSettings(unittest.TestCase): diff --git a/frappe/integrations/doctype/connected_app/test_connected_app.py b/frappe/integrations/doctype/connected_app/test_connected_app.py index b4304f6ee8..d1ff19ecb2 100644 --- a/frappe/integrations/doctype/connected_app/test_connected_app.py +++ b/frappe/integrations/doctype/connected_app/test_connected_app.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors # See license.txt -from __future__ import unicode_literals - import unittest import requests from urllib.parse import urljoin diff --git a/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py index 539fc417f2..d34e65de50 100644 --- a/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/integrations/doctype/google_drive/test_google_drive.py b/frappe/integrations/doctype/google_drive/test_google_drive.py index f06e13572c..96e8577c7c 100644 --- a/frappe/integrations/doctype/google_drive/test_google_drive.py +++ b/frappe/integrations/doctype/google_drive/test_google_drive.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/integrations/doctype/google_settings/google_settings.py b/frappe/integrations/doctype/google_settings/google_settings.py index ecc975235a..9a3f3c8ae2 100644 --- a/frappe/integrations/doctype/google_settings/google_settings.py +++ b/frappe/integrations/doctype/google_settings/google_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/integration_request/integration_request.py b/frappe/integrations/doctype/integration_request/integration_request.py index f1d59beb5a..4c4961d96d 100644 --- a/frappe/integrations/doctype/integration_request/integration_request.py +++ b/frappe/integrations/doctype/integration_request/integration_request.py @@ -2,11 +2,9 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document import json -from six import string_types from frappe.integrations.utils import json_handler class IntegrationRequest(Document): @@ -25,14 +23,14 @@ class IntegrationRequest(Document): def handle_success(self, response): """update the output field with the response along with the relevant status""" - if isinstance(response, string_types): + if isinstance(response, str): response = json.loads(response) self.db_set("status", "Completed") self.db_set("output", json.dumps(response, default=json_handler)) def handle_failure(self, response): """update the error field with the response along with the relevant status""" - if isinstance(response, string_types): + if isinstance(response, str): response = json.loads(response) self.db_set("status", "Failed") self.db_set("error", json.dumps(response, default=json_handler)) \ No newline at end of file diff --git a/frappe/integrations/doctype/integration_request/test_integration_request.py b/frappe/integrations/doctype/integration_request/test_integration_request.py index 6b77b57de4..a26eb4ba93 100644 --- a/frappe/integrations/doctype/integration_request/test_integration_request.py +++ b/frappe/integrations/doctype/integration_request/test_integration_request.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py b/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py index f9f2adeed0..b6bb77d964 100644 --- a/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py +++ b/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 80dfef2693..acc8b96679 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _, safe_encode from frappe.model.document import Document @@ -80,7 +79,7 @@ class LDAPSettings(Document): def sync_roles(self, user, additional_groups=None): - current_roles = set([d.role for d in user.get("roles")]) + current_roles = set(d.role for d in user.get("roles")) needed_roles = set() needed_roles.add(self.default_role) diff --git a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py index e6cf4eef3a..113692b6c4 100644 --- a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py b/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py index f08e7eb5bb..0c7f02844c 100644 --- a/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py +++ b/frappe/integrations/doctype/oauth_authorization_code/oauth_authorization_code.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py b/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py index cecf187e61..6084dd64b4 100644 --- a/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py +++ b/frappe/integrations/doctype/oauth_authorization_code/test_oauth_authorization_code.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py b/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py index 09fd29075b..916d0205d2 100644 --- a/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py +++ b/frappe/integrations/doctype/oauth_bearer_token/oauth_bearer_token.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py b/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py index af7de360ab..6028cebcf9 100644 --- a/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py +++ b/frappe/integrations/doctype/oauth_bearer_token/test_oauth_bearer_token.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.py b/frappe/integrations/doctype/oauth_client/oauth_client.py index 02f5041dfb..0b449ff968 100644 --- a/frappe/integrations/doctype/oauth_client/oauth_client.py +++ b/frappe/integrations/doctype/oauth_client/oauth_client.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document diff --git a/frappe/integrations/doctype/oauth_client/test_oauth_client.py b/frappe/integrations/doctype/oauth_client/test_oauth_client.py index ee119455e5..a4e50e15d8 100644 --- a/frappe/integrations/doctype/oauth_client/test_oauth_client.py +++ b/frappe/integrations/doctype/oauth_client/test_oauth_client.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2015, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py index 2bf086e0fe..3ab5df92ac 100644 --- a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py +++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2015, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ diff --git a/frappe/integrations/doctype/oauth_scope/oauth_scope.py b/frappe/integrations/doctype/oauth_scope/oauth_scope.py index a5dfe7e1ce..ae579e6b51 100644 --- a/frappe/integrations/doctype/oauth_scope/oauth_scope.py +++ b/frappe/integrations/doctype/oauth_scope/oauth_scope.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/integrations/doctype/paypal_settings/paypal_settings.py b/frappe/integrations/doctype/paypal_settings/paypal_settings.py index efd1b03355..da045d2c6a 100644 --- a/frappe/integrations/doctype/paypal_settings/paypal_settings.py +++ b/frappe/integrations/doctype/paypal_settings/paypal_settings.py @@ -63,12 +63,11 @@ More Details: """ -from __future__ import unicode_literals import frappe import json import pytz from frappe import _ -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode from frappe.model.document import Document from frappe.integrations.utils import create_request_log, make_post_request, create_payment_gateway from frappe.utils import get_url, call_hook_method, cint, get_datetime diff --git a/frappe/integrations/doctype/paytm_settings/paytm_settings.py b/frappe/integrations/doctype/paytm_settings/paytm_settings.py index 616c3837d4..9f15d73f09 100644 --- a/frappe/integrations/doctype/paytm_settings/paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/paytm_settings.py @@ -2,10 +2,9 @@ # Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import json import requests -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode import frappe from frappe.model.document import Document @@ -59,7 +58,7 @@ 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.verify_transaction" diff --git a/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py index 77a16c82ae..a00ce86327 100644 --- a/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py +++ b/frappe/integrations/doctype/paytm_settings/test_paytm_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2020, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - # import frappe import unittest diff --git a/frappe/integrations/doctype/query_parameters/query_parameters.py b/frappe/integrations/doctype/query_parameters/query_parameters.py index bfb8eae0b6..13fb94dbe3 100644 --- a/frappe/integrations/doctype/query_parameters/query_parameters.py +++ b/frappe/integrations/doctype/query_parameters/query_parameters.py @@ -2,7 +2,6 @@ # 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 diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index af7686c9b0..d24e15f480 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -60,14 +60,13 @@ For razorpay payment status is Authorized """ -from __future__ import unicode_literals import frappe from frappe import _ import json import hmac import razorpay import hashlib -from six.moves.urllib.parse import urlencode +from urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp from frappe.integrations.utils import (make_get_request, make_post_request, create_request_log, diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py index 308d34c5c2..1346811652 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt - -from __future__ import print_function, unicode_literals import os import os.path import frappe diff --git a/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py index 04d90f9b44..3aecdf3489 100755 --- a/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/test_s3_backup_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestS3BackupSettings(unittest.TestCase): diff --git a/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py b/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py index 8756d19c88..a970fc1f11 100644 --- a/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py +++ b/frappe/integrations/doctype/slack_webhook_url/slack_webhook_url.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils import get_url_to_form diff --git a/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py b/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py index a7f9316ddd..4285c2c4bc 100644 --- a/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py +++ b/frappe/integrations/doctype/slack_webhook_url/test_slack_webhook_url.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestSlackWebhookURL(unittest.TestCase): diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.py b/frappe/integrations/doctype/social_login_key/social_login_key.py index dffb730513..4a4fcd44f4 100644 --- a/frappe/integrations/doctype/social_login_key/social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/social_login_key.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe, json from frappe import _ from frappe.model.document import Document diff --git a/frappe/integrations/doctype/social_login_key/test_social_login_key.py b/frappe/integrations/doctype/social_login_key/test_social_login_key.py index e0b99ad391..23effd6a44 100644 --- a/frappe/integrations/doctype/social_login_key/test_social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/test_social_login_key.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe from frappe.integrations.doctype.social_login_key.social_login_key import BaseUrlNotSetError import unittest diff --git a/frappe/integrations/doctype/social_login_keys/social_login_keys.py b/frappe/integrations/doctype/social_login_keys/social_login_keys.py index bd4cea01af..da9e21cd8e 100644 --- a/frappe/integrations/doctype/social_login_keys/social_login_keys.py +++ b/frappe/integrations/doctype/social_login_keys/social_login_keys.py @@ -1,5 +1,4 @@ # see license -from __future__ import unicode_literals from frappe.model.document import Document class SocialLoginKeys(Document): diff --git a/frappe/integrations/doctype/stripe_settings/stripe_settings.py b/frappe/integrations/doctype/stripe_settings/stripe_settings.py index 70ca6002e4..9bb9c60775 100644 --- a/frappe/integrations/doctype/stripe_settings/stripe_settings.py +++ b/frappe/integrations/doctype/stripe_settings/stripe_settings.py @@ -2,11 +2,10 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _ -from six.moves.urllib.parse import urlencode +from 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 diff --git a/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py b/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py index 39e128192f..ba11c3c38b 100644 --- a/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py +++ b/frappe/integrations/doctype/stripe_settings/test_stripe_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestStripeSettings(unittest.TestCase): diff --git a/frappe/integrations/doctype/token_cache/test_token_cache.py b/frappe/integrations/doctype/token_cache/test_token_cache.py index 7aa069647d..2ffd57403b 100644 --- a/frappe/integrations/doctype/token_cache/test_token_cache.py +++ b/frappe/integrations/doctype/token_cache/test_token_cache.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe diff --git a/frappe/integrations/doctype/token_cache/token_cache.py b/frappe/integrations/doctype/token_cache/token_cache.py index 7cac58fae0..3001d12b2b 100644 --- a/frappe/integrations/doctype/token_cache/token_cache.py +++ b/frappe/integrations/doctype/token_cache/token_cache.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from datetime import datetime, timedelta import frappe diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py index 19233bd175..b92497f16c 100644 --- a/frappe/integrations/doctype/webhook/__init__.py +++ b/frappe/integrations/doctype/webhook/__init__.py @@ -2,8 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import frappe @@ -21,7 +19,7 @@ def run_webhooks(doc, method): if webhooks is None: # query webhooks webhooks_list = frappe.get_all('Webhook', - fields=["name", "`condition`", "webhook_docevent", "webhook_doctype"], + fields=["name", "`condition`", "webhook_docevent", "webhook_doctype"], filters={"enabled": True} ) diff --git a/frappe/integrations/doctype/webhook/test_webhook.py b/frappe/integrations/doctype/webhook/test_webhook.py index acf2f609e7..09ad56a190 100644 --- a/frappe/integrations/doctype/webhook/test_webhook.py +++ b/frappe/integrations/doctype/webhook/test_webhook.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe @@ -86,7 +84,7 @@ class TestWebhook(unittest.TestCase): # Insert the user to db self.test_user.insert() - + self.assertTrue("User" in frappe.flags.webhooks) # only 1 hook (enabled) must be queued self.assertEqual( @@ -95,7 +93,7 @@ class TestWebhook(unittest.TestCase): ) self.assertTrue(self.test_user.email in frappe.flags.webhooks_executed) self.assertEqual( - frappe.flags.webhooks_executed.get(self.test_user.email)[0], + frappe.flags.webhooks_executed.get(self.test_user.email)[0], self.sample_webhooks[0].name ) diff --git a/frappe/integrations/doctype/webhook/webhook.py b/frappe/integrations/doctype/webhook/webhook.py index ad64d9f714..1fb2bc6743 100644 --- a/frappe/integrations/doctype/webhook/webhook.py +++ b/frappe/integrations/doctype/webhook/webhook.py @@ -2,8 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import base64 import datetime import hashlib @@ -12,7 +10,7 @@ import json from time import sleep import requests -from six.moves.urllib.parse import urlparse +from urllib.parse import urlparse import frappe from frappe import _ diff --git a/frappe/integrations/doctype/webhook_data/webhook_data.py b/frappe/integrations/doctype/webhook_data/webhook_data.py index b7d989410f..dbd9328482 100644 --- a/frappe/integrations/doctype/webhook_data/webhook_data.py +++ b/frappe/integrations/doctype/webhook_data/webhook_data.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/doctype/webhook_header/webhook_header.py b/frappe/integrations/doctype/webhook_header/webhook_header.py index 11d3ee4085..428b287db2 100644 --- a/frappe/integrations/doctype/webhook_header/webhook_header.py +++ b/frappe/integrations/doctype/webhook_header/webhook_header.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/integrations/oauth2_logins.py b/frappe/integrations/oauth2_logins.py index 14a6bcc417..c38b43beb7 100644 --- a/frappe/integrations/oauth2_logins.py +++ b/frappe/integrations/oauth2_logins.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import frappe.utils from frappe.utils.oauth import login_via_oauth2, login_via_oauth2_id_token @@ -33,7 +32,7 @@ def login_via_salesforce(code, state): @frappe.whitelist(allow_guest=True) def login_via_fairlogin(code, state): - login_via_oauth2("fairlogin", code, state, decoder=decoder_compat) + login_via_oauth2("fairlogin", code, state, decoder=decoder_compat) @frappe.whitelist(allow_guest=True) def custom(code, state): diff --git a/frappe/integrations/offsite_backup_utils.py b/frappe/integrations/offsite_backup_utils.py index 48a2c89107..7a263e9d04 100644 --- a/frappe/integrations/offsite_backup_utils.py +++ b/frappe/integrations/offsite_backup_utils.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import glob import os diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index 1af9682073..09c20568b5 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -2,11 +2,9 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import json,datetime -from six.moves.urllib.parse import parse_qs -from six import string_types, text_type +from urllib.parse import parse_qs from frappe.utils import get_request_session from frappe import _ @@ -50,10 +48,10 @@ def make_post_request(url, auth=None, headers=None, data=None): raise exc def create_request_log(data, integration_type, service_name, name=None, error=None): - if isinstance(data, string_types): + if isinstance(data, str): data = json.loads(data) - if isinstance(error, string_types): + if isinstance(error, str): error = json.loads(error) integration_request = frappe.get_doc({ @@ -116,4 +114,4 @@ def create_payment_gateway(gateway, settings=None, controller=None): def json_handler(obj): if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)): - return text_type(obj) + return str(obj) diff --git a/frappe/middlewares.py b/frappe/middlewares.py index 252be56c47..05944ec37a 100644 --- a/frappe/middlewares.py +++ b/frappe/middlewares.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe import os from werkzeug.exceptions import NotFound diff --git a/frappe/migrate.py b/frappe/migrate.py index 619510fe5e..061e4c98d7 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import json import os import sys @@ -15,7 +13,7 @@ from frappe.utils.connections import check_connection from frappe.utils.dashboard import sync_dashboards from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications -from frappe.website import render +from frappe.website.utils import clear_website_cache from frappe.core.doctype.language.language import sync_languages from frappe.modules.utils import sync_customizations from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs @@ -78,7 +76,7 @@ Otherwise, check the server logs and ensure that all the required services are r frappe.get_doc('Portal Settings', 'Portal Settings').sync_menu() # syncs statics - render.clear_cache() + clear_website_cache() # updating installed applications data frappe.get_single('Installed Applications').update_versions() diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 205b451336..75122f5aba 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -2,7 +2,6 @@ # MIT License. See license.txt # model __init__.py -from __future__ import unicode_literals import frappe data_fieldtypes = ( @@ -166,7 +165,7 @@ def delete_fields(args_dict, delete=0): frappe.db.sql(""" DELETE FROM `tabSingles` WHERE doctype='%s' AND field IN (%s) - """ % (dt, ", ".join(["'{}'".format(f) for f in fields]))) + """ % (dt, ", ".join("'{}'".format(f) for f in fields))) else: existing_fields = frappe.db.multisql({ "mariadb": "DESC `tab%s`" % dt, @@ -189,7 +188,7 @@ def delete_fields(args_dict, delete=0): frappe.db.commit() query = "ALTER TABLE `tab%s` " % dt + \ - ", ".join(["DROP COLUMN `%s`" % f for f in fields_need_to_delete]) + ", ".join("DROP COLUMN `%s`" % f for f in fields_need_to_delete) frappe.db.sql(query) if frappe.db.db_type == 'postgres': diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 54d77ba988..af696e116d 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -1,9 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals -from six import iteritems, string_types - import frappe import datetime from frappe import _ @@ -109,7 +105,7 @@ class BaseDocument(object): if key in d: self.set(key, d.get(key)) - for key, value in iteritems(d): + for key, value in d.items(): self.set(key, value) return self @@ -120,7 +116,7 @@ class BaseDocument(object): if "doctype" in d: self.set("doctype", d.get("doctype")) - for key, value in iteritems(d): + for key, value in d.items(): # dont_update_if_missing is a list of fieldnames, for which, you don't want to set default value if (self.get(key) is None) and (value is not None) and (key not in self.dont_update_if_missing): self.set(key, value) @@ -358,7 +354,7 @@ class BaseDocument(object): frappe.db.sql("""INSERT INTO `tab{doctype}` ({columns}) VALUES ({values})""".format( doctype = self.doctype, - columns = ", ".join(["`"+c+"`" for c in columns]), + columns = ", ".join("`"+c+"`" for c in columns), values = ", ".join(["%s"] * len(columns)) ), list(d.values())) except Exception as e: @@ -401,7 +397,7 @@ class BaseDocument(object): frappe.db.sql("""UPDATE `tab{doctype}` SET {values} WHERE `name`=%s""".format( doctype = self.doctype, - values = ", ".join(["`"+c+"`=%s" for c in columns]) + values = ", ".join("`"+c+"`=%s" for c in columns) ), list(d.values()) + [name]) except Exception as e: if frappe.db.is_unique_key_violation(e): @@ -670,7 +666,7 @@ class BaseDocument(object): if data_field_options == "URL": if not data: continue - + frappe.utils.validate_url(data, throw=True) def _validate_constants(self): @@ -705,7 +701,7 @@ class BaseDocument(object): type_map = frappe.db.type_map - for fieldname, value in iteritems(self.get_valid_dict()): + for fieldname, value in self.get_valid_dict().items(): df = self.meta.get_field(fieldname) if not df or df.fieldtype == 'Check': @@ -770,7 +766,7 @@ class BaseDocument(object): return for fieldname, value in self.get_valid_dict().items(): - if not value or not isinstance(value, string_types): + if not value or not isinstance(value, str): continue value = frappe.as_unicode(value) @@ -839,7 +835,7 @@ class BaseDocument(object): :param parentfield: If fieldname is in child table.""" from frappe.model.meta import get_field_precision - if parentfield and not isinstance(parentfield, string_types): + if parentfield and not isinstance(parentfield, str): parentfield = parentfield.parentfield cache_key = parentfield or "main" @@ -986,7 +982,7 @@ def _filter(data, filters, limit=None): fval = ("not None", fval) elif fval is False: fval = ("None", fval) - elif isinstance(fval, string_types) and fval.startswith("^"): + elif isinstance(fval, str) and fval.startswith("^"): fval = ("^", fval[1:]) else: fval = ("=", fval) @@ -995,7 +991,7 @@ def _filter(data, filters, limit=None): for d in data: add = True - for f, fval in iteritems(_filters): + for f, fval in _filters.items(): if not frappe.compare(getattr(d, f, None), fval[0], fval[1]): add = False break diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index dc4fd97e4c..fba6765479 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals """ Create a new document with defaults set """ diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index e0c3406c46..7ed681644f 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -1,10 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals - -from six import iteritems, string_types - """build query for doclistview and return results""" import frappe.defaults @@ -48,23 +43,26 @@ class DatabaseQuery(object): # filters and fields swappable # its hard to remember what comes first - if (isinstance(fields, dict) - or (isinstance(fields, list) and fields and isinstance(fields[0], list))): + if ( + isinstance(fields, dict) + or ( + fields + and isinstance(fields, list) + and isinstance(fields[0], list) + ) + ): # if fields is given as dict/list of list, its probably filters filters, fields = fields, filters elif fields and isinstance(filters, list) \ - and len(filters) > 1 and isinstance(filters[0], string_types): + and len(filters) > 1 and isinstance(filters[0], str): # if `filters` is a list of strings, its probably fields filters, fields = fields, filters if fields: self.fields = fields else: - if pluck: - self.fields = ["`tab{0}`.`{1}`".format(self.doctype, pluck)] - else: - self.fields = ["`tab{0}`.`name`".format(self.doctype)] + self.fields = [f"`tab{self.doctype}`.`{pluck or 'name'}`"] if start: limit_start = start if page_length: limit_page_length = page_length @@ -75,7 +73,7 @@ class DatabaseQuery(object): self.docstatus = docstatus or [] self.group_by = group_by self.order_by = order_by - self.limit_start = 0 if (limit_start is False) else cint(limit_start) + self.limit_start = cint(limit_start) self.limit_page_length = cint(limit_page_length) if limit_page_length else None self.with_childnames = with_childnames self.debug = debug @@ -162,11 +160,10 @@ class DatabaseQuery(object): # left join parent, child tables for child in self.tables[1:]: - args.tables += " {join} {child} on ({child}.parent = {main}.name)".format(join=self.join, - child=child, main=self.tables[0]) + args.tables += f" {self.join} {child} on ({child}.parent = {self.tables[0]}.name)" if self.grouped_or_conditions: - self.conditions.append("({0})".format(" or ".join(self.grouped_or_conditions))) + self.conditions.append(f"({' or '.join(self.grouped_or_conditions)})") args.conditions = ' and '.join(self.conditions) @@ -191,9 +188,9 @@ class DatabaseQuery(object): fields.append(field) elif "as" in field.lower().split(" "): col, _, new = field.split() - fields.append("`{0}` as {1}".format(col, new)) + fields.append(f"`{col}` as {new}") else: - fields.append("`{0}`".format(field)) + fields.append(f"`{field}`") args.fields = ", ".join(fields) @@ -209,7 +206,7 @@ class DatabaseQuery(object): def parse_args(self): """Convert fields and filters from strings to list, dicts""" - if isinstance(self.fields, string_types): + if isinstance(self.fields, str): if self.fields == "*": self.fields = ["*"] else: @@ -223,13 +220,13 @@ class DatabaseQuery(object): for filter_name in ["filters", "or_filters"]: filters = getattr(self, filter_name) - if isinstance(filters, string_types): + if isinstance(filters, str): filters = json.loads(filters) if isinstance(filters, dict): fdict = filters filters = [] - for key, value in iteritems(fdict): + for key, value in fdict.items(): filters.append(make_filter_tuple(self.doctype, key, value)) setattr(self, filter_name, filters) @@ -265,10 +262,10 @@ class DatabaseQuery(object): if any(keyword in field.lower().split() for keyword in blacklisted_keywords): _raise_exception() - if any("({0}".format(keyword) in field.lower() for keyword in blacklisted_keywords): + if any(f"({keyword}" in field.lower() for keyword in blacklisted_keywords): _raise_exception() - if any("{0}(".format(keyword) in field.lower() for keyword in blacklisted_functions): + if any(f"{keyword}(" in field.lower() for keyword in blacklisted_functions): _raise_exception() if '@' in field.lower(): @@ -292,22 +289,30 @@ class DatabaseQuery(object): def extract_tables(self): """extract tables from fields""" - self.tables = ['`tab' + self.doctype + '`'] - + self.tables = [f"`tab{self.doctype}`"] + sql_functions = [ + "dayofyear(", + "extract(", + "locate(", + "strpos(", + "count(", + "sum(", + "avg(", + ] # add tables from fields if self.fields: - for f in self.fields: - if ( not ("tab" in f and "." in f) ) or ("locate(" in f) or ("strpos(" in f) or \ - ("count(" in f) or ("avg(" in f) or ("sum(" in f) or ("extract(" in f) or ("dayofyear(" in f): + for field in self.fields: + if not ("tab" in field and "." in field) or any(x for x in sql_functions if x in field): continue - table_name = f.split('.')[0] + table_name = field.split('.')[0] + if table_name.lower().startswith('group_concat('): table_name = table_name[13:] if table_name.lower().startswith('ifnull('): table_name = table_name[7:] if not table_name[0]=='`': - table_name = '`' + table_name + '`' + table_name = f"`{table_name}`" if not table_name in self.tables: self.append_table(table_name) @@ -316,8 +321,7 @@ class DatabaseQuery(object): doctype = table_name[4:-1] ptype = 'select' if frappe.only_has_select_perm(doctype) else 'read' - if (not self.flags.ignore_permissions) and\ - (not frappe.has_permission(doctype, ptype=ptype)): + if not self.flags.ignore_permissions and not frappe.has_permission(doctype, ptype=ptype): frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(doctype)) raise frappe.PermissionError(doctype) @@ -331,7 +335,7 @@ class DatabaseQuery(object): if len(self.tables) > 1: for idx, field in enumerate(self.fields): if '.' not in field and not _in_standard_sql_methods(field): - self.fields[idx] = '{0}.{1}'.format(self.tables[0], field) + self.fields[idx] = f"{self.tables[0]}.{field}" def get_table_columns(self): try: @@ -357,7 +361,7 @@ class DatabaseQuery(object): # remove from filters to_remove = [] for each in self.filters: - if isinstance(each, string_types): + if isinstance(each, str): each = [each] for element in each: @@ -380,7 +384,7 @@ class DatabaseQuery(object): if not self.flags.ignore_permissions: match_conditions = self.build_match_conditions() if match_conditions: - self.conditions.append("(" + match_conditions + ")") + self.conditions.append(f"({match_conditions})") def build_filter_conditions(self, filters, conditions, ignore_permissions=None): """build conditions from user filters""" @@ -391,7 +395,7 @@ class DatabaseQuery(object): filters = [filters] for f in filters: - if isinstance(f, string_types): + if isinstance(f, str): conditions.append(f) else: conditions.append(self.prepare_filter_condition(f)) @@ -412,8 +416,7 @@ class DatabaseQuery(object): if 'ifnull(' in f.fieldname: column_name = f.fieldname else: - column_name = '{tname}.{fname}'.format(tname=tname, - fname=f.fieldname) + column_name = f"{tname}.{f.fieldname}" can_be_null = True @@ -455,7 +458,7 @@ class DatabaseQuery(object): fallback = "''" value = [frappe.db.escape((v.name or '').strip(), percent=False) for v in result] if len(value): - value = "({0})".format(", ".join(value)) + value = f"({', '.join(value)})" else: value = "('')" # changing operator to IN as the above code fetches all the parent / child values and convert into tuple @@ -471,7 +474,7 @@ class DatabaseQuery(object): fallback = "''" value = [frappe.db.escape((v or '').strip(), percent=False) for v in values] if len(value): - value = "({0})".format(", ".join(value)) + value = f"({', '.join(value)})" else: value = "('')" else: @@ -508,7 +511,7 @@ class DatabaseQuery(object): can_be_null = True if 'ifnull' not in column_name: - column_name = 'ifnull({}, {})'.format(column_name, fallback) + column_name = f'ifnull({column_name}, {fallback})' elif df and df.fieldtype=="Date": value = frappe.db.format_date(f.value) @@ -522,12 +525,12 @@ class DatabaseQuery(object): value = get_time(f.value).strftime("%H:%M:%S.%f") fallback = "'00:00:00'" - elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, string_types) and + elif f.operator.lower() in ("like", "not like") or (isinstance(f.value, str) and (not df or df.fieldtype not in ["Float", "Int", "Currency", "Percent", "Check"])): value = "" if f.value==None else f.value fallback = "''" - if f.operator.lower() in ("like", "not like") and isinstance(value, string_types): + if f.operator.lower() in ("like", "not like") and isinstance(value, str): # because "like" uses backslash (\) for escaping value = value.replace("\\", "\\\\").replace("%", "%%") @@ -544,22 +547,20 @@ class DatabaseQuery(object): fallback = 0 # escape value - if isinstance(value, string_types) and not f.operator.lower() == 'between': - value = "{0}".format(frappe.db.escape(value, percent=False)) + if isinstance(value, str) and not f.operator.lower() == 'between': + value = f"{frappe.db.escape(value, percent=False)}" - if (self.ignore_ifnull + if ( + self.ignore_ifnull or not can_be_null or (f.value and f.operator.lower() in ('=', 'like')) - or 'ifnull(' in column_name.lower()): + or 'ifnull(' in column_name.lower() + ): if f.operator.lower() == 'like' and frappe.conf.get('db_type') == 'postgres': f.operator = 'ilike' - condition = '{column_name} {operator} {value}'.format( - column_name=column_name, operator=f.operator, - value=value) + condition = f'{column_name} {f.operator} {value}' else: - condition = 'ifnull({column_name}, {fallback}) {operator} {value}'.format( - column_name=column_name, fallback=fallback, operator=f.operator, - value=value) + condition = f'ifnull({column_name}, {fallback}) {f.operator} {value}' return condition @@ -577,10 +578,12 @@ class DatabaseQuery(object): role_permissions = frappe.permissions.get_role_permissions(meta, user=self.user) self.shared = frappe.share.get_shared(self.doctype, self.user) - if (not meta.istable and + if ( + not meta.istable and not (role_permissions.get("select") or role_permissions.get("read")) and not self.flags.ignore_permissions and - not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype)): + not has_any_user_permission_for_doctype(self.doctype, self.user, self.reference_doctype) + ): only_if_shared = True if not self.shared: frappe.throw(_("No permission to read {0}").format(self.doctype), frappe.PermissionError) @@ -590,8 +593,10 @@ class DatabaseQuery(object): else: #if has if_owner permission skip user perm check if role_permissions.get("has_if_owner_enabled") and role_permissions.get("if_owner", {}): - self.match_conditions.append("`tab{0}`.`owner` = {1}".format(self.doctype, - frappe.db.escape(self.user, percent=False))) + self.match_conditions.append( + f"`tab{self.doctype}`.`owner` = {frappe.db.escape(self.user, percent=False)}" + ) + # add user permission only if role has read perm elif role_permissions.get("read") or role_permissions.get("select"): # get user permissions @@ -610,8 +615,7 @@ class DatabaseQuery(object): # share is an OR condition, if there is a role permission if not only_if_shared and self.shared and conditions: - conditions = "({conditions}) or ({shared_condition})".format( - conditions=conditions, shared_condition=self.get_share_condition()) + conditions = f"({conditions}) or ({self.get_share_condition()})" return conditions @@ -619,8 +623,7 @@ class DatabaseQuery(object): return self.match_filters def get_share_condition(self): - return """`tab{0}`.name in ({1})""".format(self.doctype, ", ".join(["%s"] * len(self.shared))) % \ - tuple([frappe.db.escape(s, percent=False) for s in self.shared]) + return f"`tab{self.doctype}`.name in ({', '.join(frappe.db.escape(s, percent=False) for s in self.shared)})" def add_user_permissions(self, user_permissions): meta = frappe.get_meta(self.doctype) @@ -645,9 +648,7 @@ class DatabaseQuery(object): if frappe.get_system_settings("apply_strict_user_permissions"): condition = "" else: - empty_value_condition = "ifnull(`tab{doctype}`.`{fieldname}`, '')=''".format( - doctype=self.doctype, fieldname=df.get('fieldname') - ) + empty_value_condition = f"ifnull(`tab{self.doctype}`.`{df.get('fieldname')}`, '')=''" condition = empty_value_condition + " or " for permission in user_permission_values: @@ -655,9 +656,7 @@ class DatabaseQuery(object): docs.append(permission.get('doc')) # append docs based on user permission applicable on reference doctype - # this is useful when getting list of docs from a link field - # in this case parent doctype of the link # will be the reference doctype @@ -669,14 +668,9 @@ class DatabaseQuery(object): docs.append(permission.get('doc')) if docs: - condition += "`tab{doctype}`.`{fieldname}` in ({values})".format( - doctype=self.doctype, - fieldname=df.get('fieldname'), - values=", ".join( - [(frappe.db.escape(doc, percent=False)) for doc in docs]) - ) - - match_conditions.append("({condition})".format(condition=condition)) + values = ", ".join(frappe.db.escape(doc, percent=False) for doc in docs) + condition += f"`tab{self.doctype}`.`{df.get('fieldname')}` in ({values})" + match_conditions.append(f"({condition})") match_filters[df.get('options')] = docs if match_conditions: @@ -726,17 +720,17 @@ class DatabaseQuery(object): # `idx desc, modified desc` # will covert to # `tabItem`.`idx` desc, `tabItem`.`modified` desc - args.order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(self.doctype, - f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')]) + args.order_by = ', '.join( + f"`tab{self.doctype}`.`{f.split()[0].strip()}` {f.split()[1].strip()}" for f in meta.sort_field.split(',') + ) else: sort_field = meta.sort_field or 'modified' sort_order = (meta.sort_field and meta.sort_order) or 'desc' - - args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, sort_field or "modified", sort_order or "desc") + args.order_by = f"`tab{self.doctype}`.`{sort_field or 'modified'}` {sort_order or 'desc'}" # draft docs always on top - if meta.is_submittable: - args.order_by = "`tab{0}`.docstatus asc, {1}".format(self.doctype, args.order_by) + if hasattr(meta, 'is_submittable') and meta.is_submittable: + args.order_by = f"`tab{self.doctype}`.docstatus asc, {args.order_by}" def validate_order_by_and_group_by(self, parameters): """Check order by, group by so that atleast one column is selected and does not have subquery""" @@ -807,17 +801,16 @@ def get_order_by(doctype, meta): # `idx desc, modified desc` # will covert to # `tabItem`.`idx` desc, `tabItem`.`modified` desc - order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(doctype, - f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')]) + order_by = ', '.join(f"`tab{doctype}`.`{f.split()[0].strip()}` {f.split()[1].strip()}" for f in meta.sort_field.split(',')) + else: sort_field = meta.sort_field or 'modified' sort_order = (meta.sort_field and meta.sort_order) or 'desc' - - order_by = "`tab{0}`.`{1}` {2}".format(doctype, sort_field or "modified", sort_order or "desc") + order_by = f"`tab{doctype}`.`{sort_field or 'modified'}` {sort_order or 'desc'}" # draft docs always on top if meta.is_submittable: - order_by = "`tab{0}`.docstatus asc, {1}".format(doctype, order_by) + order_by = f"`tab{doctype}`.docstatus asc, {order_by}" return order_by diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 5fcc74a734..cc88cfa106 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -1,9 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import os -from six import string_types, integer_types import shutil import frappe @@ -17,7 +15,7 @@ from frappe.utils.password import delete_all_passwords_for from frappe.model.naming import revert_series_if_last from frappe.utils.global_search import delete_for_document from frappe.desk.doctype.tag.tag import delete_tags_for_document -from frappe.exceptions import FileNotFoundError + doctypes_to_skip = ("Communication", "ToDo", "DocShare", "Email Unsubscribe", "Activity Log", "File", "Version", "Document Follow", "Comment" , "View Log", "Tag Link", "Notification Log", "Email Queue") @@ -35,7 +33,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa name = frappe.form_dict.get('dn') names = name - if isinstance(name, string_types) or isinstance(name, integer_types): + if isinstance(name, str) or isinstance(name, int): names = [name] for name in names or []: diff --git a/frappe/model/docfield.py b/frappe/model/docfield.py index 19b78e329d..6360c3866d 100644 --- a/frappe/model/docfield.py +++ b/frappe/model/docfield.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals """docfield utililtes""" import frappe diff --git a/frappe/model/document.py b/frappe/model/document.py index 623916597e..61160e1f01 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1,14 +1,11 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function import frappe import time from frappe import _, msgprint, is_whitelisted from frappe.utils import flt, cstr, now, get_datetime_str, file_lock, date_diff from frappe.model.base_document import BaseDocument, get_controller from frappe.model.naming import set_new_name -from six import iteritems, string_types from werkzeug.exceptions import NotFound, Forbidden import hashlib, json from frappe.model import optional_fields, table_fields @@ -18,6 +15,7 @@ from frappe.utils.global_search import update_global_search from frappe.integrations.doctype.webhook import run_webhooks from frappe.desk.form.document_follow import follow_document from frappe.core.doctype.server_script.server_script_utils import run_server_script_for_doc_event +from frappe.utils.data import get_absolute_url # once_only validation # methods @@ -53,7 +51,7 @@ def get_doc(*args, **kwargs): if isinstance(args[0], BaseDocument): # already a document return args[0] - elif isinstance(args[0], string_types): + elif isinstance(args[0], str): doctype = args[0] elif isinstance(args[0], dict): @@ -90,7 +88,7 @@ class Document(BaseDocument): self._default_new_docs = {} self.flags = frappe._dict() - if args and args[0] and isinstance(args[0], string_types): + if args and args[0] and isinstance(args[0], str): # first arugment is doctype if len(args)==1: # single @@ -437,7 +435,7 @@ class Document(BaseDocument): def get_values(): values = self.as_dict() # format values - for key, value in iteritems(values): + for key, value in values.items(): if value==None: values[key] = "" return values @@ -454,7 +452,7 @@ class Document(BaseDocument): def update_single(self, d): """Updates values for Single type Document in `tabSingles`.""" frappe.db.sql("""delete from `tabSingles` where doctype=%s""", self.doctype) - for field, value in iteritems(d): + for field, value in d.items(): if field != "doctype": frappe.db.sql("""insert into `tabSingles` (doctype, field, value) values (%s, %s, %s)""", (self.doctype, field, value)) @@ -1202,8 +1200,8 @@ class Document(BaseDocument): doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.parentfield))) def get_url(self): - """Returns Desk URL for this document. `/app/Form/{doctype}/{name}`""" - return "/app/Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name) + """Returns Desk URL for this document.""" + return get_absolute_url(self.doctype, self.name) def add_comment(self, comment_type='Comment', text=None, comment_email=None, link_doctype=None, link_name=None, comment_by=None): """Add a comment to this document. diff --git a/frappe/model/dynamic_links.py b/frappe/model/dynamic_links.py index 7404ba407e..676c86d7da 100644 --- a/frappe/model/dynamic_links.py +++ b/frappe/model/dynamic_links.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe # select doctypes that are accessed by the user (not read_only) first, so that the diff --git a/frappe/model/mapper.py b/frappe/model/mapper.py index d3014435e0..fa8858d950 100644 --- a/frappe/model/mapper.py +++ b/frappe/model/mapper.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt +import json -from __future__ import unicode_literals -import frappe, json +import frappe from frappe import _ -from frappe.utils import cstr from frappe.model import default_fields, table_fields -from six import string_types +from frappe.utils import cstr + @frappe.whitelist() def make_mapped_doc(method, source_name, selected_children=None, args=None): @@ -60,7 +60,7 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, # main if not target_doc: target_doc = frappe.new_doc(table_maps[from_doctype]["doctype"]) - elif isinstance(target_doc, string_types): + elif isinstance(target_doc, str): target_doc = frappe.get_doc(json.loads(target_doc)) if (not apply_strict_user_permissions @@ -137,10 +137,8 @@ def get_mapped_doc(from_doctype, from_docname, table_maps, target_doc=None, def map_doc(source_doc, target_doc, table_map, source_parent=None): if table_map.get("validation"): for key, condition in table_map["validation"].items(): - if condition[0]=="=": - if source_doc.get(key) != condition[1]: - frappe.throw(_("Cannot map because following condition fails: ") - + key + "=" + cstr(condition[1])) + if condition[0] == "=" and source_doc.get(key) != condition[1]: + frappe.throw(_("Cannot map because following condition fails:") + f" {key}={cstr(condition[1])}") map_fields(source_doc, target_doc, table_map, source_parent) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 66e8b08d92..b212324208 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -14,10 +14,7 @@ Example: ''' - -from __future__ import unicode_literals, print_function from datetime import datetime -from six.moves import range import frappe, json, os from frappe.utils import cstr, cint, cast_fieldtype from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields @@ -667,7 +664,7 @@ def trim_tables(doctype=None): and not f.startswith("_")] if columns_to_remove: print(doctype, "columns removed:", columns_to_remove) - columns_to_remove = ", ".join(["drop `{0}`".format(c) for c in columns_to_remove]) + columns_to_remove = ", ".join("drop `{0}`".format(c) for c in columns_to_remove) query = """alter table `tab{doctype}` {columns}""".format( doctype=doctype, columns=columns_to_remove) frappe.db.sql_ddl(query) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index b8d6a6f8d7..fe136adce8 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -1,12 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import now_datetime, cint, cstr import re -from six import string_types from frappe.model import log_types @@ -146,7 +144,7 @@ def make_autoname(key="", doctype="", doc=""): def parse_naming_series(parts, doctype='', doc=''): n = '' - if isinstance(parts, string_types): + if isinstance(parts, str): parts = parts.split('.') series_set = False today = now_datetime() @@ -177,7 +175,7 @@ def parse_naming_series(parts, doctype='', doc=''): else: part = e - if isinstance(part, string_types): + if isinstance(part, str): n += part return n @@ -203,7 +201,7 @@ def revert_series_if_last(key, name, doc=None): Reverts the series for particular naming series: * key is naming series - SINV-.YYYY-.#### * name is actual name - SINV-2021-0001 - + 1. This function split the key into two parts prefix (SINV-YYYY) & hashes (####). 2. Use prefix to get the current index of that naming series from Series table 3. Then revert the current index. @@ -213,7 +211,7 @@ def revert_series_if_last(key, name, doc=None): 2. If hash doesn't exit in hashes, we get the hash from prefix, then update name and prefix accordingly. *Example:* - 1. key = SINV-.YYYY.- + 1. key = SINV-.YYYY.- * If key doesn't have hash it will add hash at the end * prefix will be SINV-YYYY based on this will get current index from Series table. 2. key = SINV-.####.-2021 @@ -221,9 +219,9 @@ def revert_series_if_last(key, name, doc=None): * will search hash in key then accordingly get prefix = SINV- 3. key = ####.-2021 * prefix = #### and hashes = 2021 (hash doesn't exist) - * will search hash in key then accordingly get prefix = "" + * will search hash in key then accordingly get prefix = "" """ - if ".#" in key: + if ".#" in key: prefix, hashes = key.rsplit(".", 1) if "#" not in hashes: # get the hash part from the key diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 2c9dc5d823..9b8ac2574d 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import print_function, unicode_literals - import frappe from frappe import _, bold from frappe.model.dynamic_links import get_dynamic_link_map @@ -144,7 +141,7 @@ def update_user_settings(old, new, link_fields): if not link_fields: return # find the user settings for the linked doctypes - linked_doctypes = set([d.parent for d in link_fields if not d.issingle]) + linked_doctypes = {d.parent for d in link_fields if not d.issingle} user_settings_details = frappe.db.sql('''SELECT `user`, `doctype`, `data` FROM `__UserSettings` WHERE `data` like %s diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 61983d322c..836f70dd55 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function """ Sync's doctype and docfields from txt files to database perms will get synced only if none exist @@ -84,7 +82,7 @@ def get_doc_files(files, start_path): document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', 'data_migration_mapping', 'data_migration_plan', 'workspace', - 'onboarding_step', 'module_onboarding'] + 'onboarding_step', 'module_onboarding', 'form_tour'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py index efbe46a4ab..47615182e4 100644 --- a/frappe/model/utils/__init__.py +++ b/frappe/model/utils/__init__.py @@ -1,15 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function -from six.moves import range import frappe from frappe import _ from frappe.utils import cstr from frappe.build import html_to_js_template import re -from six import text_type - import io STANDARD_FIELD_CONVERSION_MAP = { diff --git a/frappe/model/utils/link_count.py b/frappe/model/utils/link_count.py index 5faa5ba44b..7562aaae45 100644 --- a/frappe/model/utils/link_count.py +++ b/frappe/model/utils/link_count.py @@ -1,10 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe -from six import iteritems ignore_doctypes = ("DocType", "Print Format", "Role", "Module Def", "Communication", "ToDo") @@ -39,7 +36,7 @@ def update_link_count(): link_count = frappe.cache().get_value('_link_count') if link_count: - for key, count in iteritems(link_count): + for key, count in link_count.items(): if key[0] not in ignore_doctypes: try: frappe.db.sql('update `tab{0}` set idx = idx + {1} where name=%s'.format(key[0], count), diff --git a/frappe/model/utils/rename_field.py b/frappe/model/utils/rename_field.py index 778f623092..9fe9d64041 100644 --- a/frappe/model/utils/rename_field.py +++ b/frappe/model/utils/rename_field.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function - import frappe import json from frappe.model import no_value_fields, table_fields diff --git a/frappe/model/utils/user_settings.py b/frappe/model/utils/user_settings.py index d59bda71a5..ad378ab93f 100644 --- a/frappe/model/utils/user_settings.py +++ b/frappe/model/utils/user_settings.py @@ -1,9 +1,8 @@ -from __future__ import unicode_literals + # Settings saved per user basis # such as page_limit, filters, last_view import frappe, json -from six import iteritems, string_types from frappe import safe_decode # dict for mapping the index and index type for the filters of different views @@ -36,7 +35,7 @@ def update_user_settings(doctype, user_settings, for_update=False): else: current = json.loads(get_user_settings(doctype, for_update = True)) - if isinstance(current, string_types): + if isinstance(current, str): # corrupt due to old code, remove this in a future release current = {} @@ -47,7 +46,7 @@ def update_user_settings(doctype, user_settings, for_update=False): def sync_user_settings(): '''Sync from cache to database (called asynchronously via the browser)''' - for key, data in iteritems(frappe.cache().hgetall('_user_settings')): + for key, data in frappe.cache().hgetall('_user_settings').items(): key = safe_decode(key) doctype, user = key.split('::') # WTF? frappe.db.multisql({ diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 3e8125f9b1..fa2f557370 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -1,11 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.utils import cint from frappe import _ -from six import string_types import json class WorkflowStateError(frappe.ValidationError): pass @@ -268,7 +266,7 @@ def print_workflow_log(messages, title, doctype, indicator): @frappe.whitelist() def get_common_transition_actions(docs, doctype): common_actions = [] - if isinstance(docs, string_types): + if isinstance(docs, str): docs = json.loads(docs) try: for (i, doc) in enumerate(docs, 1): diff --git a/frappe/modules/__init__.py b/frappe/modules/__init__.py index fef4829bb6..33411f8d74 100644 --- a/frappe/modules/__init__.py +++ b/frappe/modules/__init__.py @@ -1,2 +1,2 @@ -from __future__ import unicode_literals + from .utils import * \ No newline at end of file diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 4b22c82105..ae9f11d53b 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, os import frappe.model from frappe.modules import scrub, get_module_path, scrub_dt_dn diff --git a/frappe/modules/import_file.py b/frappe/modules/import_file.py index fdfd00404c..e743f0c3da 100644 --- a/frappe/modules/import_file.py +++ b/frappe/modules/import_file.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function - import frappe, os, json from frappe.modules import get_module_path, scrub_dt_dn from frappe.utils import get_datetime_str diff --git a/frappe/modules/patch_handler.py b/frappe/modules/patch_handler.py index 0ed10d1e0d..029234d5d9 100644 --- a/frappe/modules/patch_handler.py +++ b/frappe/modules/patch_handler.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function """ Execute Patch Files @@ -14,9 +12,6 @@ from __future__ import unicode_literals, print_function """ import frappe, frappe.permissions, time -# for patches -import os - class PatchError(Exception): pass def run_all(skip_failing=False): diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 132aa1e2a5..0f3e57a5a0 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function """ Utilities for using modules """ diff --git a/frappe/monitor.py b/frappe/monitor.py index 6802a59584..34ca7d67f7 100644 --- a/frappe/monitor.py +++ b/frappe/monitor.py @@ -2,8 +2,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - from datetime import datetime import json import traceback diff --git a/frappe/oauth.py b/frappe/oauth.py index a4c66bf3f2..67d346ad8a 100644 --- a/frappe/oauth.py +++ b/frappe/oauth.py @@ -486,6 +486,7 @@ class OAuthWebRequestValidator(RequestValidator): user = None payload = jwt.decode( id_token_hint, + algorithms=["HS256"], options={ "verify_signature": False, "verify_aud": False, @@ -508,7 +509,7 @@ class OAuthWebRequestValidator(RequestValidator): id_token_hint, key=client_secret, audience=client_id, - algorithm="HS256", + algorithms=["HS256"], options={ "verify_exp": False, }, diff --git a/frappe/parallel_test_runner.py b/frappe/parallel_test_runner.py index 1dbb24f191..2f83b88572 100644 --- a/frappe/parallel_test_runner.py +++ b/frappe/parallel_test_runner.py @@ -114,13 +114,30 @@ class ParallelTestRunner(): # Generate coverage report only for app that is being tested source_path = os.path.join(get_bench_path(), 'apps', self.app) - omit=['*.html', '*.js', '*.xml', '*.css', '*.less', '*.scss', - '*.vue', '*/doctype/*/*_dashboard.py', '*/patches/*'] + incl = [ + '*.py', + ] + omit = [ + '*.js', + '*.xml', + '*.pyc', + '*.css', + '*.less', + '*.scss', + '*.vue', + '*.pyc', + '*.html', + '*/test_*', + '*/node_modules/*', + '*/doctype/*/*_dashboard.py', + '*/patches/*', + ] if self.app == 'frappe': + omit.append('*/tests/*') omit.append('*/commands/*') - self.coverage = Coverage(source=[source_path], omit=omit) + self.coverage = Coverage(source=[source_path], omit=omit, include=incl) self.coverage.start() def save_coverage(self): diff --git a/frappe/patches.txt b/frappe/patches.txt index e70be0a37b..7605d8ea2b 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -1,11 +1,5 @@ frappe.patches.v12_0.remove_deprecated_fields_from_doctype #3 -execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.', '.v4_0.')""") #2014-05-12 -frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4 execute:frappe.utils.global_search.setup_global_search_table() -frappe.patches.v8_0.update_global_search_table -frappe.patches.v7_0.update_auth -frappe.patches.v8_0.drop_in_dialog #2017-09-22 -frappe.patches.v7_2.remove_in_filter execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23 execute:frappe.reload_doc('core', 'doctype', 'doctype_link', force=True) #2020-10-17 execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22 @@ -14,7 +8,6 @@ frappe.patches.v11_0.drop_column_apply_user_permissions execute:frappe.reload_doc('core', 'doctype', 'custom_docperm') execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29 execute:frappe.reload_doc('core', 'doctype', 'comment') -frappe.patches.v8_0.drop_is_custom_from_docperm execute:frappe.reload_doc('core', 'doctype', 'document_naming_rule', force=True) execute:frappe.reload_doc('core', 'doctype', 'module_def') #2020-08-28 execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01 @@ -25,190 +18,40 @@ execute:frappe.reload_doc('core', 'doctype', 'communication') #2019-10-02 execute:frappe.reload_doc('core', 'doctype', 'server_script') frappe.patches.v11_0.replicate_old_user_permissions frappe.patches.v11_0.reload_and_rename_view_log #2019-01-03 -frappe.patches.v7_1.rename_scheduler_log_to_error_log -frappe.patches.v6_1.rename_file_data -frappe.patches.v7_0.re_route #2016-06-27 -frappe.patches.v8_0.update_records_in_global_search #11-05-2017 -frappe.patches.v8_0.update_published_in_global_search frappe.patches.v11_0.copy_fetch_data_from_options frappe.patches.v11_0.change_email_signature_fieldtype execute:frappe.reload_doc('core', 'doctype', 'activity_log') execute:frappe.reload_doc('core', 'doctype', 'deleted_document') execute:frappe.reload_doc('core', 'doctype', 'domain_settings') frappe.patches.v13_0.rename_custom_client_script -frappe.patches.v8_0.rename_page_role_to_has_role #2017-03-16 -frappe.patches.v7_2.setup_custom_perms #2017-01-19 -frappe.patches.v8_0.set_user_permission_for_page_and_report #2017-03-20 execute:frappe.reload_doc('core', 'doctype', 'role') #2017-05-23 execute:frappe.reload_doc('core', 'doctype', 'user') #2017-10-27 -execute:frappe.reload_doc('custom', 'doctype', 'custom_field') #2015-10-19 -execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26 execute:frappe.reload_doc('core', 'doctype', 'report_column') execute:frappe.reload_doc('core', 'doctype', 'report_filter') execute:frappe.reload_doc('core', 'doctype', 'report') #2020-08-25 -execute:frappe.reload_doc('core', 'doctype', 'translation') #2016-03-03 -execute:frappe.reload_doc('email', 'doctype', 'email_alert') #2014-07-15 -execute:frappe.reload_doc('desk', 'doctype', 'todo') #2014-12-31-1 -execute:frappe.reload_doc('custom', 'doctype', 'property_setter') #2014-12-31-1 -execute:frappe.reload_doc('core', 'doctype', 'patch_log') #2016-10-31 -execute:frappe.reload_doctype("File") # 2015-10-19 execute:frappe.reload_doc('core', 'doctype', 'error_snapshot') -execute:frappe.clear_cache() -frappe.patches.v7_1.rename_scheduler_log_to_error_log -frappe.patches.v7_1.sync_language_doctype -frappe.patches.v7_0.rename_bulk_email_to_email_queue -frappe.patches.v7_1.rename_chinese_language_codes - -execute:frappe.db.sql("alter table `tabSessions` modify `user` varchar(255), engine=InnoDB") -execute:frappe.db.sql("delete from `tabDocField` where parent='0'") -frappe.patches.v4_0.change_varchar_length -frappe.patches.v6_4.reduce_varchar_length -frappe.patches.v5_2.change_checks_to_not_null -frappe.patches.v6_9.int_float_not_null #2015-11-25 -frappe.patches.v5_0.v4_to_v5 - -frappe.patches.v5_0.remove_shopping_cart_app -frappe.patches.v4_0.webnotes_to_frappe -execute:frappe.permissions.reset_perms("Module Def") -execute:import frappe.installer;frappe.installer.make_site_dirs() #2014-02-19 -frappe.patches.v4_0.rename_profile_to_user -frappe.patches.v4_0.deprecate_control_panel -frappe.patches.v4_0.remove_old_parent -frappe.patches.v4_0.rename_sitemap_to_route -frappe.patches.v4_0.website_sitemap_hierarchy -frappe.patches.v4_0.remove_index_sitemap -frappe.patches.v4_0.set_website_route_idx -frappe.patches.v4_0.add_delete_permission -frappe.patches.v4_0.set_todo_checked_as_closed -frappe.patches.v4_0.private_backups -frappe.patches.v4_0.set_module_in_report -frappe.patches.v4_0.update_datetime -frappe.patches.v4_0.file_manager_hooks execute:frappe.get_doc("User", "Guest").save() -frappe.patches.v4_0.update_custom_field_insert_after -frappe.patches.v4_0.deprecate_link_selects -frappe.patches.v4_0.set_user_gravatar -frappe.patches.v4_0.set_user_permissions -frappe.patches.v4_0.create_custom_field_for_owner_match -frappe.patches.v4_0.enable_scheduler_in_system_settings -execute:frappe.db.sql("update tabReport set apply_user_permissions=1") #2014-06-03 -frappe.patches.v4_0.replace_deprecated_timezones -execute:import frappe.website.render; frappe.website.render.clear_cache("login"); #2014-06-10 -frappe.patches.v4_0.fix_attach_field_file_url -execute:frappe.permissions.reset_perms("User") #2015-03-24 -execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '')='' or ifnull(`role`, '')=''""") #2014-08-18 -frappe.patches.v4_0.remove_user_owner_custom_field -execute:frappe.delete_doc("DocType", "Website Template") -execute:frappe.db.sql("""update `tabProperty Setter` set property_type='Text' where property in ('options', 'default')""") #2014-06-20 -frappe.patches.v4_1.enable_outgoing_email_settings -execute:frappe.db.sql("""update `tabSingles` set `value`=`doctype` where `field`='name'""") #2014-07-04 -frappe.patches.v4_1.enable_print_as_pdf #2014-06-17 -execute:frappe.db.sql("""update `tabDocPerm` set email=1 where parent='User' and permlevel=0 and `role`='All' and `read`=1 and apply_user_permissions=1""") #2014-07-15 -execute:frappe.db.sql("""update `tabPrint Format` set print_format_type='Client' where ifnull(print_format_type, '')=''""") #2014-07-28 -frappe.patches.v4_1.file_manager_fix -frappe.patches.v4_2.print_with_letterhead execute:frappe.delete_doc("DocType", "Control Panel", force=1) -execute:frappe.reload_doc('website', 'doctype', 'web_form') #2014-09-04 -execute:frappe.reload_doc('website', 'doctype', 'web_form_field') #2014-09-04 -frappe.patches.v4_2.refactor_website_routing -frappe.patches.v4_2.set_assign_in_doc -frappe.patches.v4_3.remove_allow_on_submit_customization -frappe.patches.v5_0.rename_table_fieldnames -frappe.patches.v5_0.communication_parent -frappe.patches.v5_0.clear_website_group_and_notifications -frappe.patches.v5_0.update_shared -execute:frappe.reload_doc("core", "doctype", "docshare") #2015-07-21 -frappe.patches.v6_19.comment_feed_communication -frappe.patches.v6_16.star_to_like -frappe.patches.v5_0.bookmarks_to_stars -frappe.patches.v5_0.style_settings_to_website_theme -frappe.patches.v5_0.rename_ref_type_fieldnames -frappe.patches.v5_0.fix_email_alert -frappe.patches.v5_0.fix_null_date_datetime -frappe.patches.v5_0.force_sync_website execute:frappe.delete_doc("DocType", "Tag") execute:frappe.db.sql("delete from `tabProperty Setter` where `property` in ('idx', '_idx')") -frappe.patches.v5_0.move_scheduler_last_event_to_system_settings execute:frappe.db.sql("update tabUser set new_password='' where ifnull(new_password, '')!=''") -frappe.patches.v5_0.fix_text_editor_file_urls -frappe.patches.v5_0.modify_session -frappe.patches.v5_0.expire_old_scheduler_logs execute:frappe.permissions.reset_perms("DocType") execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'") -frappe.patches.v6_0.communication_status_and_permission -frappe.patches.v6_0.make_task_log_folder -frappe.patches.v6_0.document_type_rename -frappe.patches.v6_0.fix_ghana_currency -frappe.patches.v6_2.ignore_user_permissions_if_missing execute:frappe.db.sql("delete from tabSessions where user is null") -frappe.patches.v6_2.rename_backup_manager execute:frappe.delete_doc("DocType", "Backup Manager") -execute:frappe.db.sql("""update `tabCommunication` set parenttype=null, parent=null, parentfield=null""") #2015-10-22 execute:frappe.permissions.reset_perms("Web Page") -frappe.patches.v6_6.user_last_active -frappe.patches.v6_6.fix_file_url -frappe.patches.v6_11.rename_field_in_email_account -frappe.patches.v7_0.create_private_file_folder -frappe.patches.v6_15.remove_property_setter_for_previous_field #2015-12-29 -frappe.patches.v6_15.set_username execute:frappe.permissions.reset_perms("Error Snapshot") -frappe.patches.v6_16.feed_doc_owner -frappe.patches.v6_21.print_settings_repeat_header_footer -frappe.patches.v6_24.set_language_as_code -frappe.patches.v6_20x.update_insert_after -frappe.patches.v6_20x.set_allow_draft_for_print -frappe.patches.v6_20x.remove_roles_from_website_user -frappe.patches.v7_0.set_user_fullname -frappe.patches.v7_0.add_communication_in_doc -frappe.patches.v7_0.update_send_after_in_bulk_email -execute:frappe.db.sql('''delete from `tabSingles` where doctype="Email Settings"''') # 2016-06-13 execute:frappe.db.sql("delete from `tabWeb Page` where ifnull(template_path, '')!=''") -frappe.patches.v7_0.rename_newsletter_list_to_email_group -frappe.patches.v7_0.set_email_group -frappe.patches.v7_1.setup_integration_services #2016-10-27 -frappe.patches.v7_1.rename_chinese_language_codes execute:frappe.core.doctype.language.language.update_language_names() # 2017-04-12 execute:frappe.db.set_value("Print Settings", "Print Settings", "add_draft_heading", 1) -frappe.patches.v7_0.cleanup_list_settings execute:frappe.db.set_default('language', '') -frappe.patches.v7_1.refactor_integration_broker -frappe.patches.v7_1.set_backup_limit -frappe.patches.v7_2.set_doctype_engine -frappe.patches.v7_2.merge_knowledge_base -frappe.patches.v7_0.update_report_builder_json -frappe.patches.v7_2.set_in_standard_filter_property #1 -frappe.patches.v8_0.drop_unwanted_indexes execute:frappe.db.sql("update tabCommunication set communication_date = creation where time(communication_date) = 0") -frappe.patches.v7_2.fix_email_queue_recipient -frappe.patches.v7_2.update_feedback_request # 2017-02-27 execute:frappe.rename_doc('Country', 'Macedonia, Republic of', 'Macedonia', ignore_if_exists=True) execute:frappe.rename_doc('Country', 'Iran, Islamic Republic of', 'Iran', ignore_if_exists=True) execute:frappe.rename_doc('Country', 'Tanzania, United Republic of', 'Tanzania', ignore_if_exists=True) execute:frappe.rename_doc('Country', 'Syrian Arab Republic', 'Syria', ignore_if_exists=True) -frappe.patches.v8_0.rename_listsettings_to_usersettings -frappe.patches.v7_2.update_communications -frappe.patches.v8_0.deprecate_integration_broker -frappe.patches.v8_0.update_gender_and_salutation -frappe.patches.v8_0.setup_email_inbox #2017-03-29 -frappe.patches.v8_0.newsletter_childtable_migrate -frappe.patches.v8_0.set_doctype_values_in_custom_role -frappe.patches.v8_0.install_new_build_system_requirements -frappe.patches.v8_0.set_currency_field_precision # 2017-05-09 execute:frappe.reload_doc('desk', 'doctype', 'notification_log') -frappe.patches.v8_0.rename_print_to_printing -frappe.patches.v7_1.disabled_print_settings_for_custom_print_format execute:frappe.db.sql('update tabReport set module="Desk" where name="ToDo"') -frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings -frappe.patches.v8_1.update_format_options_in_auto_email_report -frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists -frappe.patches.v8_5.delete_email_group_member_with_invalid_emails -frappe.patches.v8_x.update_user_permission -frappe.patches.v8_5.patch_event_colors -frappe.patches.v8_10.delete_static_web_page_from_global_search -frappe.patches.v9_1.add_sms_sender_name_as_parameters -frappe.patches.v9_1.resave_domain_settings -frappe.patches.v9_1.revert_domain_settings -frappe.patches.v9_1.move_feed_to_activity_log execute:frappe.delete_doc('Page', 'data-import-tool', ignore_missing=True) frappe.patches.v10_0.reload_countries_and_currencies # 2021-02-03 frappe.patches.v10_0.refactor_social_login_keys diff --git a/frappe/patches/v10_0/enable_chat_by_default_within_system_settings.py b/frappe/patches/v10_0/enable_chat_by_default_within_system_settings.py index eddca78051..24f915c512 100644 --- a/frappe/patches/v10_0/enable_chat_by_default_within_system_settings.py +++ b/frappe/patches/v10_0/enable_chat_by_default_within_system_settings.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v10_0/enhance_security.py b/frappe/patches/v10_0/enhance_security.py index 865d18dcff..4f6ca4faa1 100644 --- a/frappe/patches/v10_0/enhance_security.py +++ b/frappe/patches/v10_0/enhance_security.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe from frappe.utils import cint diff --git a/frappe/patches/v10_0/increase_single_table_column_length.py b/frappe/patches/v10_0/increase_single_table_column_length.py index 18de0cff9e..e578d192fc 100644 --- a/frappe/patches/v10_0/increase_single_table_column_length.py +++ b/frappe/patches/v10_0/increase_single_table_column_length.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + """ Run this after updating country_info.json and or """ diff --git a/frappe/patches/v10_0/migrate_passwords_passlib.py b/frappe/patches/v10_0/migrate_passwords_passlib.py index 22b7a86f85..d0b36efbaa 100644 --- a/frappe/patches/v10_0/migrate_passwords_passlib.py +++ b/frappe/patches/v10_0/migrate_passwords_passlib.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.utils.password import LegacyPassword diff --git a/frappe/patches/v10_0/modify_naming_series_table.py b/frappe/patches/v10_0/modify_naming_series_table.py index 659e247a38..ca6114eb55 100644 --- a/frappe/patches/v10_0/modify_naming_series_table.py +++ b/frappe/patches/v10_0/modify_naming_series_table.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - ''' Modify the Integer 10 Digits Value to BigInt 20 Digit value to generate long Naming Series diff --git a/frappe/patches/v10_0/modify_smallest_currency_fraction.py b/frappe/patches/v10_0/modify_smallest_currency_fraction.py index f875d6b87d..c9ae477359 100644 --- a/frappe/patches/v10_0/modify_smallest_currency_fraction.py +++ b/frappe/patches/v10_0/modify_smallest_currency_fraction.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v10_0/refactor_social_login_keys.py b/frappe/patches/v10_0/refactor_social_login_keys.py index 07737912df..a3f08939ec 100644 --- a/frappe/patches/v10_0/refactor_social_login_keys.py +++ b/frappe/patches/v10_0/refactor_social_login_keys.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe from frappe.utils import cstr diff --git a/frappe/patches/v10_0/reload_countries_and_currencies.py b/frappe/patches/v10_0/reload_countries_and_currencies.py index f83ed9c3aa..8d019a4855 100644 --- a/frappe/patches/v10_0/reload_countries_and_currencies.py +++ b/frappe/patches/v10_0/reload_countries_and_currencies.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + """ Run this after updating country_info.json and or """ diff --git a/frappe/patches/v10_0/remove_custom_field_for_disabled_domain.py b/frappe/patches/v10_0/remove_custom_field_for_disabled_domain.py index f27639388e..54839cfe02 100644 --- a/frappe/patches/v10_0/remove_custom_field_for_disabled_domain.py +++ b/frappe/patches/v10_0/remove_custom_field_for_disabled_domain.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v10_0/set_default_locking_time.py b/frappe/patches/v10_0/set_default_locking_time.py index 1c9797a6cc..045fa0e3fa 100644 --- a/frappe/patches/v10_0/set_default_locking_time.py +++ b/frappe/patches/v10_0/set_default_locking_time.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v10_0/set_no_copy_to_workflow_state.py b/frappe/patches/v10_0/set_no_copy_to_workflow_state.py index 800d4a4d1b..eb469b8452 100644 --- a/frappe/patches/v10_0/set_no_copy_to_workflow_state.py +++ b/frappe/patches/v10_0/set_no_copy_to_workflow_state.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/change_email_signature_fieldtype.py b/frappe/patches/v11_0/change_email_signature_fieldtype.py index f6d4bd5dcb..ccfa8541c3 100644 --- a/frappe/patches/v11_0/change_email_signature_fieldtype.py +++ b/frappe/patches/v11_0/change_email_signature_fieldtype.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v11_0/copy_fetch_data_from_options.py b/frappe/patches/v11_0/copy_fetch_data_from_options.py index ae7788450a..e256c7085f 100644 --- a/frappe/patches/v11_0/copy_fetch_data_from_options.py +++ b/frappe/patches/v11_0/copy_fetch_data_from_options.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/create_contact_for_user.py b/frappe/patches/v11_0/create_contact_for_user.py index b4722ab3ae..5a483b630e 100644 --- a/frappe/patches/v11_0/create_contact_for_user.py +++ b/frappe/patches/v11_0/create_contact_for_user.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.core.doctype.user.user import create_contact import re @@ -8,7 +8,6 @@ def execute(): frappe.reload_doc('integrations', 'doctype', 'google_contacts') frappe.reload_doc('contacts', 'doctype', 'contact') frappe.reload_doc('core', 'doctype', 'dynamic_link') - frappe.reload_doc('communication', 'doctype', 'call_log') contact_meta = frappe.get_meta("Contact") if contact_meta.has_field("phone_nos") and contact_meta.has_field("email_ids"): diff --git a/frappe/patches/v11_0/delete_all_prepared_reports.py b/frappe/patches/v11_0/delete_all_prepared_reports.py index 1d722da7e6..77f041e3ee 100644 --- a/frappe/patches/v11_0/delete_all_prepared_reports.py +++ b/frappe/patches/v11_0/delete_all_prepared_reports.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/delete_duplicate_user_permissions.py b/frappe/patches/v11_0/delete_duplicate_user_permissions.py index 9d9d516ac5..518c1f7714 100644 --- a/frappe/patches/v11_0/delete_duplicate_user_permissions.py +++ b/frappe/patches/v11_0/delete_duplicate_user_permissions.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/drop_column_apply_user_permissions.py b/frappe/patches/v11_0/drop_column_apply_user_permissions.py index 4f46bc0907..629d5a5da4 100644 --- a/frappe/patches/v11_0/drop_column_apply_user_permissions.py +++ b/frappe/patches/v11_0/drop_column_apply_user_permissions.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/fix_order_by_in_reports_json.py b/frappe/patches/v11_0/fix_order_by_in_reports_json.py index 2cd82d442d..096e0e7654 100644 --- a/frappe/patches/v11_0/fix_order_by_in_reports_json.py +++ b/frappe/patches/v11_0/fix_order_by_in_reports_json.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe, json def execute(): diff --git a/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py b/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py index f7b9e476a9..a099b89b40 100644 --- a/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py +++ b/frappe/patches/v11_0/make_all_prepared_report_attachments_private.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe diff --git a/frappe/patches/v11_0/migrate_report_settings_for_new_listview.py b/frappe/patches/v11_0/migrate_report_settings_for_new_listview.py index 5bef52c295..e5b18368db 100644 --- a/frappe/patches/v11_0/migrate_report_settings_for_new_listview.py +++ b/frappe/patches/v11_0/migrate_report_settings_for_new_listview.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe, json def execute(): diff --git a/frappe/patches/v11_0/multiple_references_in_events.py b/frappe/patches/v11_0/multiple_references_in_events.py index 57d4787eca..9fa5968d8e 100644 --- a/frappe/patches/v11_0/multiple_references_in_events.py +++ b/frappe/patches/v11_0/multiple_references_in_events.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/reload_and_rename_view_log.py b/frappe/patches/v11_0/reload_and_rename_view_log.py index 12c71b746f..fa0432c4e2 100644 --- a/frappe/patches/v11_0/reload_and_rename_view_log.py +++ b/frappe/patches/v11_0/reload_and_rename_view_log.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py index e2c2ef5f0e..5c54b1e5c1 100644 --- a/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py +++ b/frappe/patches/v11_0/remove_doctype_user_permissions_for_page_and_report.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v11_0/remove_skip_for_doctype.py b/frappe/patches/v11_0/remove_skip_for_doctype.py index edd385e317..638a5a0fd7 100644 --- a/frappe/patches/v11_0/remove_skip_for_doctype.py +++ b/frappe/patches/v11_0/remove_skip_for_doctype.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.desk.form.linked_with import get_linked_doctypes from frappe.patches.v11_0.replicate_old_user_permissions import get_doctypes_to_skip diff --git a/frappe/patches/v11_0/rename_email_alert_to_notification.py b/frappe/patches/v11_0/rename_email_alert_to_notification.py index 727055fcc4..365b76ea48 100644 --- a/frappe/patches/v11_0/rename_email_alert_to_notification.py +++ b/frappe/patches/v11_0/rename_email_alert_to_notification.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.model.rename_doc import rename_doc diff --git a/frappe/patches/v11_0/rename_google_maps_doctype.py b/frappe/patches/v11_0/rename_google_maps_doctype.py index 5420dcfc20..8091154b9c 100644 --- a/frappe/patches/v11_0/rename_google_maps_doctype.py +++ b/frappe/patches/v11_0/rename_google_maps_doctype.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals + import frappe from frappe.model.rename_doc import rename_doc def execute(): if frappe.db.exists("DocType","Google Maps") and not frappe.db.exists("DocType","Google Maps Settings"): rename_doc('DocType', 'Google Maps', 'Google Maps Settings') - frappe.reload_doc('integrations', 'doctype', 'google_maps_settings') \ No newline at end of file diff --git a/frappe/patches/v11_0/rename_standard_reply_to_email_template.py b/frappe/patches/v11_0/rename_standard_reply_to_email_template.py index 06869530e2..2906085738 100644 --- a/frappe/patches/v11_0/rename_standard_reply_to_email_template.py +++ b/frappe/patches/v11_0/rename_standard_reply_to_email_template.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.model.rename_doc import rename_doc diff --git a/frappe/patches/v11_0/rename_workflow_action_to_workflow_action_master.py b/frappe/patches/v11_0/rename_workflow_action_to_workflow_action_master.py index 32f17ac2d8..9a48104611 100644 --- a/frappe/patches/v11_0/rename_workflow_action_to_workflow_action_master.py +++ b/frappe/patches/v11_0/rename_workflow_action_to_workflow_action_master.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.model.rename_doc import rename_doc diff --git a/frappe/patches/v11_0/replicate_old_user_permissions.py b/frappe/patches/v11_0/replicate_old_user_permissions.py index d1ceae8a7f..50a81b5ce7 100644 --- a/frappe/patches/v11_0/replicate_old_user_permissions.py +++ b/frappe/patches/v11_0/replicate_old_user_permissions.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe import json from frappe.utils import cint diff --git a/frappe/patches/v11_0/set_allow_self_approval_in_workflow.py b/frappe/patches/v11_0/set_allow_self_approval_in_workflow.py index 24c01e1a58..63ae5f949f 100644 --- a/frappe/patches/v11_0/set_allow_self_approval_in_workflow.py +++ b/frappe/patches/v11_0/set_allow_self_approval_in_workflow.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/set_default_letter_head_source.py b/frappe/patches/v11_0/set_default_letter_head_source.py index a43ea397e4..3639524e7d 100644 --- a/frappe/patches/v11_0/set_default_letter_head_source.py +++ b/frappe/patches/v11_0/set_default_letter_head_source.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe def execute(): diff --git a/frappe/patches/v11_0/set_dropbox_file_backup.py b/frappe/patches/v11_0/set_dropbox_file_backup.py index 884fef320e..27492b3ab2 100644 --- a/frappe/patches/v11_0/set_dropbox_file_backup.py +++ b/frappe/patches/v11_0/set_dropbox_file_backup.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + from frappe.utils import cint import frappe diff --git a/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py b/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py index 331b0eba32..a8e9bd4de1 100644 --- a/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py +++ b/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.utils.password import get_decrypted_password diff --git a/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py b/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py index 738fea1a48..55a7b74f7e 100644 --- a/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py +++ b/frappe/patches/v11_0/sync_user_permission_doctype_before_migrate.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v11_0/update_list_user_settings.py b/frappe/patches/v11_0/update_list_user_settings.py index d492ff1704..1b179d8cdf 100644 --- a/frappe/patches/v11_0/update_list_user_settings.py +++ b/frappe/patches/v11_0/update_list_user_settings.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe, json from frappe.model.utils.user_settings import update_user_settings, sync_user_settings diff --git a/frappe/patches/v12_0/create_notification_settings_for_user.py b/frappe/patches/v12_0/create_notification_settings_for_user.py index 63eeccc07a..6edfd88872 100644 --- a/frappe/patches/v12_0/create_notification_settings_for_user.py +++ b/frappe/patches/v12_0/create_notification_settings_for_user.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings diff --git a/frappe/patches/v12_0/init_desk_settings.py b/frappe/patches/v12_0/init_desk_settings.py index ecd9c94d5b..fceb44b924 100644 --- a/frappe/patches/v12_0/init_desk_settings.py +++ b/frappe/patches/v12_0/init_desk_settings.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import json import frappe from frappe.config import get_modules_from_all_apps_for_user diff --git a/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py b/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py index 040fde1bee..85be3f7feb 100644 --- a/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py +++ b/frappe/patches/v12_0/move_timeline_links_to_dynamic_links.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe def execute(): diff --git a/frappe/patches/v12_0/setup_comments_from_communications.py b/frappe/patches/v12_0/setup_comments_from_communications.py index 28c7aa93c0..039ceeff35 100644 --- a/frappe/patches/v12_0/setup_comments_from_communications.py +++ b/frappe/patches/v12_0/setup_comments_from_communications.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe def execute(): diff --git a/frappe/patches/v12_0/setup_email_linking.py b/frappe/patches/v12_0/setup_email_linking.py index 08f57ca5e4..9e939e1245 100644 --- a/frappe/patches/v12_0/setup_email_linking.py +++ b/frappe/patches/v12_0/setup_email_linking.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from frappe.desk.page.setup_wizard.install_fixtures import setup_email_linking def execute(): diff --git a/frappe/patches/v12_0/update_auto_repeat_status_and_not_submittable.py b/frappe/patches/v12_0/update_auto_repeat_status_and_not_submittable.py index d696b6c53a..3a3dcec315 100644 --- a/frappe/patches/v12_0/update_auto_repeat_status_and_not_submittable.py +++ b/frappe/patches/v12_0/update_auto_repeat_status_and_not_submittable.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_field diff --git a/frappe/patches/v13_0/add_standard_navbar_items.py b/frappe/patches/v13_0/add_standard_navbar_items.py index 9982e6e3f5..4473cb8c07 100644 --- a/frappe/patches/v13_0/add_standard_navbar_items.py +++ b/frappe/patches/v13_0/add_standard_navbar_items.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe from frappe.utils.install import add_standard_navbar_items diff --git a/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py b/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py index 29b99464b5..b5542c9c8a 100644 --- a/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py +++ b/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v13_0/add_toggle_width_in_navbar_settings.py b/frappe/patches/v13_0/add_toggle_width_in_navbar_settings.py index 59acb77480..bd3367377c 100644 --- a/frappe/patches/v13_0/add_toggle_width_in_navbar_settings.py +++ b/frappe/patches/v13_0/add_toggle_width_in_navbar_settings.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v13_0/cleanup_desk_cards.py b/frappe/patches/v13_0/cleanup_desk_cards.py index 6ac8604041..b6fab66475 100644 --- a/frappe/patches/v13_0/cleanup_desk_cards.py +++ b/frappe/patches/v13_0/cleanup_desk_cards.py @@ -1,11 +1,10 @@ import frappe -from six import string_types from json import loads from frappe.desk.doctype.workspace.workspace import get_link_type, get_report_type def execute(): frappe.reload_doc('desk', 'doctype', 'workspace') - + pages = frappe.db.sql("Select `name` from `tabDesk Page`") # pages = frappe.get_all("Workspace", filters={"is_standard": 0}, pluck="name") @@ -21,14 +20,14 @@ def rebuild_links(page): doc = frappe.get_doc("Workspace", page) except frappe.DoesNotExistError: db_doc = get_doc_from_db(page) - + doc = frappe.get_doc(db_doc) doc.insert(ignore_permissions=True) - + doc.links = [] for card in get_all_cards(page): - if isinstance(card.links, string_types): + if isinstance(card.links, str): links = loads(card.links) else: links = card.links @@ -43,7 +42,7 @@ def rebuild_links(page): for link in links: if not frappe.db.exists(get_link_type(link.get('type')), link.get('name')): continue - + doc.append('links', { "label": link.get('label') or link.get('name'), "type": "Link", @@ -53,7 +52,7 @@ def rebuild_links(page): "dependencies": ', '.join(link.get('dependencies', [])), "is_query_report": get_report_type(link.get('name')) if link.get('type').lower() == "report" else 0 }) - + try: doc.save(ignore_permissions=True) except frappe.LinkValidationError: diff --git a/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py index 1eba5871c2..776e9c796e 100644 --- a/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py +++ b/frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v13_0/delete_package_publish_tool.py b/frappe/patches/v13_0/delete_package_publish_tool.py index 25024f58dd..bf9aaf5a76 100644 --- a/frappe/patches/v13_0/delete_package_publish_tool.py +++ b/frappe/patches/v13_0/delete_package_publish_tool.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe diff --git a/frappe/patches/v13_0/enable_custom_script.py b/frappe/patches/v13_0/enable_custom_script.py index edc242e700..0684074fe7 100644 --- a/frappe/patches/v13_0/enable_custom_script.py +++ b/frappe/patches/v13_0/enable_custom_script.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v13_0/generate_theme_files_in_public_folder.py b/frappe/patches/v13_0/generate_theme_files_in_public_folder.py index bcb47bec24..dd9fb1961a 100644 --- a/frappe/patches/v13_0/generate_theme_files_in_public_folder.py +++ b/frappe/patches/v13_0/generate_theme_files_in_public_folder.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe diff --git a/frappe/patches/v13_0/jinja_hook.py b/frappe/patches/v13_0/jinja_hook.py index 84ed6e6cff..990ae50f35 100644 --- a/frappe/patches/v13_0/jinja_hook.py +++ b/frappe/patches/v13_0/jinja_hook.py @@ -1,7 +1,6 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from click import secho diff --git a/frappe/patches/v13_0/queryreport_columns.py b/frappe/patches/v13_0/queryreport_columns.py index 6c2a1b1219..5c381f4f3e 100644 --- a/frappe/patches/v13_0/queryreport_columns.py +++ b/frappe/patches/v13_0/queryreport_columns.py @@ -1,7 +1,6 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import json diff --git a/frappe/patches/v13_0/remove_duplicate_navbar_items.py b/frappe/patches/v13_0/remove_duplicate_navbar_items.py index cb4de4ca07..b6c6033f64 100644 --- a/frappe/patches/v13_0/remove_duplicate_navbar_items.py +++ b/frappe/patches/v13_0/remove_duplicate_navbar_items.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v13_0/remove_tailwind_from_page_builder.py b/frappe/patches/v13_0/remove_tailwind_from_page_builder.py index 6e7bf67bac..2bf2c7bf87 100644 --- a/frappe/patches/v13_0/remove_tailwind_from_page_builder.py +++ b/frappe/patches/v13_0/remove_tailwind_from_page_builder.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe diff --git a/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py b/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py index 7c3aec9510..3122de8bea 100644 --- a/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py +++ b/frappe/patches/v13_0/rename_list_view_setting_to_list_view_settings.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe diff --git a/frappe/patches/v13_0/rename_notification_fields.py b/frappe/patches/v13_0/rename_notification_fields.py index 2984e6503c..1413d80358 100644 --- a/frappe/patches/v13_0/rename_notification_fields.py +++ b/frappe/patches/v13_0/rename_notification_fields.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.utils.rename_field import rename_field diff --git a/frappe/patches/v13_0/rename_onboarding.py b/frappe/patches/v13_0/rename_onboarding.py index c506c6076e..852065dfd2 100644 --- a/frappe/patches/v13_0/rename_onboarding.py +++ b/frappe/patches/v13_0/rename_onboarding.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v13_0/replace_old_data_import.py b/frappe/patches/v13_0/replace_old_data_import.py index 920ee7b553..838881b48e 100644 --- a/frappe/patches/v13_0/replace_old_data_import.py +++ b/frappe/patches/v13_0/replace_old_data_import.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe diff --git a/frappe/patches/v13_0/update_date_filters_in_user_settings.py b/frappe/patches/v13_0/update_date_filters_in_user_settings.py index d4c6aa1d03..3b1b07fe0a 100644 --- a/frappe/patches/v13_0/update_date_filters_in_user_settings.py +++ b/frappe/patches/v13_0/update_date_filters_in_user_settings.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe, json from frappe.model.utils.user_settings import update_user_settings, sync_user_settings diff --git a/frappe/patches/v13_0/update_duration_options.py b/frappe/patches/v13_0/update_duration_options.py index 60eef8fc93..e0d8dea4ea 100644 --- a/frappe/patches/v13_0/update_duration_options.py +++ b/frappe/patches/v13_0/update_duration_options.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py b/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py index 93bf5c766e..ff58f99c2f 100644 --- a/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py +++ b/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import frappe def execute(): diff --git a/frappe/patches/v13_0/update_newsletter_content_type.py b/frappe/patches/v13_0/update_newsletter_content_type.py index 6f8dcc1935..5f047680ee 100644 --- a/frappe/patches/v13_0/update_newsletter_content_type.py +++ b/frappe/patches/v13_0/update_newsletter_content_type.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v13_0/update_notification_channel_if_empty.py b/frappe/patches/v13_0/update_notification_channel_if_empty.py index 2c2a40e81b..bcf9a7b28c 100644 --- a/frappe/patches/v13_0/update_notification_channel_if_empty.py +++ b/frappe/patches/v13_0/update_notification_channel_if_empty.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v13_0/web_template_set_module.py b/frappe/patches/v13_0/web_template_set_module.py index df008557d8..2ee9e3ba2d 100644 --- a/frappe/patches/v13_0/web_template_set_module.py +++ b/frappe/patches/v13_0/web_template_set_module.py @@ -1,7 +1,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe def execute(): diff --git a/frappe/patches/v4_0/add_delete_permission.py b/frappe/patches/v4_0/add_delete_permission.py deleted file mode 100644 index 091bdab3ff..0000000000 --- a/frappe/patches/v4_0/add_delete_permission.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "docperm") - - # delete same as cancel (map old permissions) - frappe.db.sql("""update tabDocPerm set `delete`=ifnull(`cancel`,0)""") - - # can't cancel if can't submit - frappe.db.sql("""update tabDocPerm set `cancel`=0 where ifnull(`submit`,0)=0""") - - frappe.clear_cache() \ No newline at end of file diff --git a/frappe/patches/v4_0/change_varchar_length.py b/frappe/patches/v4_0/change_varchar_length.py deleted file mode 100644 index 29fe8f310d..0000000000 --- a/frappe/patches/v4_0/change_varchar_length.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql('update tabDocField set search_index=0 where fieldtype="Small Text"') - frappe.db.sql('update tabDocField set in_list_view=0 where fieldtype="Image"') - - for dt in frappe.db.sql_list("""select name from `tabDocType` where issingle=0"""): - desc = dict((d["Field"], d) for d in frappe.db.sql("desc `tab{}`".format(dt), as_dict=True)) - alter_table = [] - - if desc["name"]["Type"] != "varchar(255)": - alter_table.append("change `name` `name` varchar(255) not null") - - for fieldname in ("modified_by", "owner", "parent", "parentfield", "parenttype"): - if desc[fieldname]["Type"] != "varchar(255)": - alter_table.append("change `{fieldname}` `{fieldname}` varchar(255)".format(fieldname=fieldname)) - - if alter_table: - alter_table_query = "alter table `tab{doctype}` {alter_table}".format(doctype=dt, alter_table=",\n".join(alter_table)) - # print alter_table_query - frappe.db.sql_ddl(alter_table_query) - diff --git a/frappe/patches/v4_0/create_custom_field_for_owner_match.py b/frappe/patches/v4_0/create_custom_field_for_owner_match.py deleted file mode 100644 index 60dafc27da..0000000000 --- a/frappe/patches/v4_0/create_custom_field_for_owner_match.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals, print_function -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_field - -def execute(): - if "match" in frappe.db.get_table_columns("DocPerm"): - create_custom_field_for_owner_match() - -def create_custom_field_for_owner_match(): - docperm_meta = frappe.get_meta('DocPerm') - if docperm_meta.get_field('apply_user_permissions'): - frappe.db.sql("""update `tabDocPerm` set apply_user_permissions=1 where `match`='owner'""") - - for dt in frappe.db.sql_list("""select distinct parent from `tabDocPerm` - where `match`='owner' and permlevel=0 and parent != 'User'"""): - - # a link field pointing to User already exists - if (frappe.db.get_value("DocField", {"parent": dt, "fieldtype": "Link", "options": "User", "default": "__user"}) - or frappe.db.get_value("Custom Field", {"dt": dt, "fieldtype": "Link", "options": "User", "default": "__user"})): - print("User link field already exists for", dt) - continue - - fieldname = "{}_owner".format(frappe.scrub(dt)) - - create_custom_field(dt, frappe._dict({ - "permlevel": 0, - "label": "{} Owner".format(dt), - "fieldname": fieldname, - "fieldtype": "Link", - "options": "User", - "default": "__user" - })) - - frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=owner""".format(doctype=dt, - fieldname=fieldname)) - - # commit is required so that we don't lose these changes because of an error in next loop's ddl - frappe.db.commit() diff --git a/frappe/patches/v4_0/deprecate_control_panel.py b/frappe/patches/v4_0/deprecate_control_panel.py deleted file mode 100644 index 892d3043c4..0000000000 --- a/frappe/patches/v4_0/deprecate_control_panel.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("update `tabDefaultValue` set parenttype='__default' where parenttype='Control Panel'") - frappe.db.sql("update `tabDefaultValue` set parent='__default' where parent='Control Panel'") - frappe.clear_cache() diff --git a/frappe/patches/v4_0/deprecate_link_selects.py b/frappe/patches/v4_0/deprecate_link_selects.py deleted file mode 100644 index a3243cffb8..0000000000 --- a/frappe/patches/v4_0/deprecate_link_selects.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for name in frappe.db.sql_list("""select name from `tabCustom Field` - where fieldtype="Select" and options like "link:%" """): - custom_field = frappe.get_doc("Custom Field", name) - custom_field.fieldtype = "Link" - custom_field.options = custom_field.options[5:] - custom_field.save() diff --git a/frappe/patches/v4_0/enable_scheduler_in_system_settings.py b/frappe/patches/v4_0/enable_scheduler_in_system_settings.py deleted file mode 100644 index 5d1b836270..0000000000 --- a/frappe/patches/v4_0/enable_scheduler_in_system_settings.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils.scheduler import disable_scheduler, enable_scheduler -from frappe.utils import cint - -def execute(): - frappe.reload_doc("core", "doctype", "system_settings") - if cint(frappe.db.get_global("disable_scheduler")): - disable_scheduler() - else: - enable_scheduler() diff --git a/frappe/patches/v4_0/file_manager_hooks.py b/frappe/patches/v4_0/file_manager_hooks.py deleted file mode 100644 index 6be3b25124..0000000000 --- a/frappe/patches/v4_0/file_manager_hooks.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals, print_function - -import frappe -import os -from frappe.utils import get_files_path -from frappe.core.doctype.file.file import get_content_hash - - -def execute(): - frappe.reload_doc('core', 'doctype', 'file_data') - for name, file_name, file_url in frappe.db.sql( - """select name, file_name, file_url from `tabFile` - where file_name is not null"""): - b = frappe.get_doc('File', name) - old_file_name = b.file_name - b.file_name = os.path.basename(old_file_name) - if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): - b.file_url = os.path.normpath('/' + old_file_name) - else: - b.file_url = os.path.normpath('/files/' + old_file_name) - try: - _file = frappe.get_doc("File", {"file_name": name}) - content = _file.get_content() - b.content_hash = get_content_hash(content) - except IOError: - print('Warning: Error processing ', name) - _file_name = old_file_name - b.content_hash = None - - try: - b.save() - except frappe.DuplicateEntryError: - frappe.delete_doc(b.doctype, b.name) - diff --git a/frappe/patches/v4_0/fix_attach_field_file_url.py b/frappe/patches/v4_0/fix_attach_field_file_url.py deleted file mode 100644 index c29e5763f1..0000000000 --- a/frappe/patches/v4_0/fix_attach_field_file_url.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - attach_fields = (frappe.db.sql("""select parent, fieldname from `tabDocField` where fieldtype in ('Attach', 'Attach Image')""") + - frappe.db.sql("""select dt, fieldname from `tabCustom Field` where fieldtype in ('Attach', 'Attach Image')""")) - - for doctype, fieldname in attach_fields: - frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=concat("/", `{fieldname}`) - where `{fieldname}` like 'files/%'""".format(doctype=doctype, fieldname=fieldname)) diff --git a/frappe/patches/v4_0/private_backups.py b/frappe/patches/v4_0/private_backups.py deleted file mode 100644 index 016af0615d..0000000000 --- a/frappe/patches/v4_0/private_backups.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.installer import make_site_dirs - -def execute(): - make_site_dirs() - if frappe.local.conf.backup_path and frappe.local.conf.backup_path.startswith("public"): - raise Exception("Backups path in conf set to public directory") diff --git a/frappe/patches/v4_0/remove_index_sitemap.py b/frappe/patches/v4_0/remove_index_sitemap.py deleted file mode 100644 index 5dcd0d79c7..0000000000 --- a/frappe/patches/v4_0/remove_index_sitemap.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - pass diff --git a/frappe/patches/v4_0/remove_old_parent.py b/frappe/patches/v4_0/remove_old_parent.py deleted file mode 100644 index 7717f7b7e3..0000000000 --- a/frappe/patches/v4_0/remove_old_parent.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for doctype in frappe.db.sql_list("""select name from `tabDocType` where istable=1"""): - frappe.db.sql("""delete from `tab{0}` where parent like "old_par%:%" """.format(doctype)) - frappe.db.sql("""delete from `tabDocField` where parent="0" """) diff --git a/frappe/patches/v4_0/remove_user_owner_custom_field.py b/frappe/patches/v4_0/remove_user_owner_custom_field.py deleted file mode 100644 index be6a45e090..0000000000 --- a/frappe/patches/v4_0/remove_user_owner_custom_field.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - user_owner = frappe.db.get_value("Custom Field", {"fieldname": "user_owner"}) - if user_owner: - frappe.delete_doc("Custom Field", user_owner) diff --git a/frappe/patches/v4_0/rename_profile_to_user.py b/frappe/patches/v4_0/rename_profile_to_user.py deleted file mode 100644 index 48555ead9e..0000000000 --- a/frappe/patches/v4_0/rename_profile_to_user.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.model.utils.rename_field import rename_field -from frappe.model.meta import get_table_columns - -def execute(): - tables = frappe.db.sql_list("show tables") - if "tabUser" not in tables: - frappe.rename_doc("DocType", "Profile", "User", force=True) - - frappe.reload_doc("website", "doctype", "blogger") - - if "profile" in get_table_columns("Blogger"): - rename_field("Blogger", "profile", "user") diff --git a/frappe/patches/v4_0/rename_sitemap_to_route.py b/frappe/patches/v4_0/rename_sitemap_to_route.py deleted file mode 100644 index 8ae5170b44..0000000000 --- a/frappe/patches/v4_0/rename_sitemap_to_route.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.model.utils.rename_field import rename_field - -def execute(): - tables = frappe.db.sql_list("show tables") - for doctype in ("Website Sitemap", "Website Sitemap Config"): - if "tab{}".format(doctype) in tables: - frappe.delete_doc("DocType", doctype, force=1) - frappe.db.sql("drop table `tab{}`".format(doctype)) - - for d in ("Blog Category", "Blog Post", "Web Page"): - frappe.reload_doc("website", "doctype", frappe.scrub(d)) - rename_field_if_exists(d, "parent_website_sitemap", "parent_website_route") - - for d in ("blog_category", "blog_post", "web_page", "post", "user_vote"): - frappe.reload_doc("website", "doctype", d) - -def rename_field_if_exists(doctype, old_fieldname, new_fieldname): - try: - rename_field(doctype, old_fieldname, new_fieldname) - except frappe.db.ProgrammingError as e: - if not frappe.db.is_column_missing(e): - raise diff --git a/frappe/patches/v4_0/replace_deprecated_timezones.py b/frappe/patches/v4_0/replace_deprecated_timezones.py deleted file mode 100644 index a491325ebc..0000000000 --- a/frappe/patches/v4_0/replace_deprecated_timezones.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils.momentjs import data as momentjs_data - -def execute(): - frappe.reload_doc("core", "doctype", "user") - - ss = frappe.get_doc("System Settings", "System Settings") - if ss.time_zone in momentjs_data.get("links"): - ss.time_zone = momentjs_data["links"][ss.time_zone] - ss.flags.ignore_mandatory = True - ss.save() - - for user, time_zone in frappe.db.sql("select name, time_zone from `tabUser` where ifnull(time_zone, '')!=''"): - if time_zone in momentjs_data.get("links"): - user = frappe.get_doc("User", user) - user.time_zone = momentjs_data["links"][user.time_zone] - user.save() diff --git a/frappe/patches/v4_0/set_module_in_report.py b/frappe/patches/v4_0/set_module_in_report.py deleted file mode 100644 index 9760f7efb3..0000000000 --- a/frappe/patches/v4_0/set_module_in_report.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "report") - frappe.db.sql("""update `tabReport` r set r.module=(select d.module from `tabDocType` d - where d.name=r.ref_doctype) where ifnull(r.module, '')=''""") \ No newline at end of file diff --git a/frappe/patches/v4_0/set_todo_checked_as_closed.py b/frappe/patches/v4_0/set_todo_checked_as_closed.py deleted file mode 100644 index 59e8df3793..0000000000 --- a/frappe/patches/v4_0/set_todo_checked_as_closed.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "todo") - try: - frappe.db.sql("""update tabToDo set status = if(ifnull(checked,0)=0, 'Open', 'Closed')""") - except: - pass diff --git a/frappe/patches/v4_0/set_user_gravatar.py b/frappe/patches/v4_0/set_user_gravatar.py deleted file mode 100644 index 733b9bfe11..0000000000 --- a/frappe/patches/v4_0/set_user_gravatar.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for name in frappe.db.sql_list("select name from `tabUser` where ifnull(user_image, '')=''"): - user = frappe.get_doc("User", name) - user.update_gravatar() - user.db_set("user_image", user.user_image) diff --git a/frappe/patches/v4_0/set_user_permissions.py b/frappe/patches/v4_0/set_user_permissions.py deleted file mode 100644 index 726b9ee715..0000000000 --- a/frappe/patches/v4_0/set_user_permissions.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -import frappe.permissions - -def execute(): - frappe.reload_doc("core", "doctype", "docperm") - table_columns = frappe.db.get_table_columns("DocPerm") - - if "restricted" in table_columns: - frappe.db.sql("""update `tabDocPerm` set apply_user_permissions=1 where apply_user_permissions=0 - and restricted=1""") - - if "match" in table_columns: - frappe.db.sql("""update `tabDocPerm` set apply_user_permissions=1 - where apply_user_permissions=0 and ifnull(`match`, '')!=''""") - - # change Restriction to User Permission in tabDefaultValue - frappe.db.sql("""update `tabDefaultValue` set parenttype='User Permission' where parenttype='Restriction'""") - - frappe.clear_cache() - diff --git a/frappe/patches/v4_0/set_website_route_idx.py b/frappe/patches/v4_0/set_website_route_idx.py deleted file mode 100644 index 663a324008..0000000000 --- a/frappe/patches/v4_0/set_website_route_idx.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - pass - # from frappe.website.doctype.website_template.website_template import \ - # get_pages_and_generators, get_template_controller - # - # frappe.reload_doc("website", "doctype", "website_template") - # frappe.reload_doc("website", "doctype", "website_route") - # - # for app in frappe.get_installed_apps(): - # pages, generators = get_pages_and_generators(app) - # for g in generators: - # doctype = frappe.get_attr(get_template_controller(app, g["path"], g["fname"]) + ".doctype") - # module = frappe.db.get_value("DocType", doctype, "module") - # frappe.reload_doc(frappe.scrub(module), "doctype", frappe.scrub(doctype)) - # - # frappe.db.sql("""update `tabBlog Category` set `title`=`name` where ifnull(`title`, '')=''""") - # frappe.db.sql("""update `tabWebsite Route` set idx=null""") - # for doctype in ["Blog Category", "Blog Post", "Web Page", "Website Group"]: - # frappe.db.sql("""update `tab{}` set idx=null""".format(doctype)) - # - # from frappe.website.doctype.website_template.website_template import rebuild_website_template - # rebuild_website_template() diff --git a/frappe/patches/v4_0/update_custom_field_insert_after.py b/frappe/patches/v4_0/update_custom_field_insert_after.py deleted file mode 100644 index ddb888c493..0000000000 --- a/frappe/patches/v4_0/update_custom_field_insert_after.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for d in frappe.db.sql("""select name, dt, insert_after from `tabCustom Field` - where docstatus < 2""", as_dict=1): - dt_meta = frappe.get_meta(d.dt) - if not dt_meta.get_field(d.insert_after): - cf = frappe.get_doc("Custom Field", d.name) - df = dt_meta.get("fields", {"label": d.insert_after}) - if df: - cf.insert_after = df[0].fieldname - else: - cf.insert_after = None - cf.save() diff --git a/frappe/patches/v4_0/update_datetime.py b/frappe/patches/v4_0/update_datetime.py deleted file mode 100644 index 0e91174780..0000000000 --- a/frappe/patches/v4_0/update_datetime.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for table in frappe.db.sql_list("show tables"): - for field in frappe.db.sql("desc `%s`" % table): - if field[1]=="datetime": - frappe.db.sql("alter table `%s` change `%s` `%s` datetime(6)" % \ - (table, field[0], field[0])) - elif field[1]=="time": - frappe.db.sql("alter table `%s` change `%s` `%s` time(6)" % \ - (table, field[0], field[0])) diff --git a/frappe/patches/v4_0/webnotes_to_frappe.py b/frappe/patches/v4_0/webnotes_to_frappe.py deleted file mode 100644 index 22b3848d5a..0000000000 --- a/frappe/patches/v4_0/webnotes_to_frappe.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe, json - -def execute(): - frappe.clear_cache() - installed = frappe.get_installed_apps() - if "webnotes" in installed: - installed.remove("webnotes") - if "frappe" not in installed: - installed = ["frappe"] + installed - frappe.db.set_global("installed_apps", json.dumps(installed)) - frappe.clear_cache() diff --git a/frappe/patches/v4_0/website_sitemap_hierarchy.py b/frappe/patches/v4_0/website_sitemap_hierarchy.py deleted file mode 100644 index bb22144cd7..0000000000 --- a/frappe/patches/v4_0/website_sitemap_hierarchy.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals - -import frappe - -def execute(): - # frappe.db.sql("""update `tabWebsite Route` ws set ref_doctype=(select wsc.ref_doctype - # from `tabWebsite Template` wsc where wsc.name=ws.website_template) - # where ifnull(page_or_generator, '')!='Page'""") - - frappe.reload_doc("website", "doctype", "website_settings") - - # original_home_page = frappe.db.get_value("Website Settings", "Website Settings", "home_page") - # - # home_page = frappe.db.sql("""select name from `tabWebsite Route` - # where (name=%s or docname=%s) and name!='index'""", (original_home_page, original_home_page)) - # home_page = home_page[0][0] if home_page else original_home_page - # - # frappe.db.set_value("Website Settings", "Website Settings", "home_page", home_page) diff --git a/frappe/patches/v4_1/enable_outgoing_email_settings.py b/frappe/patches/v4_1/enable_outgoing_email_settings.py deleted file mode 100644 index 7ffa84a278..0000000000 --- a/frappe/patches/v4_1/enable_outgoing_email_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "outgoing_email_settings") - if (frappe.db.get_value("Outgoing Email Settings", "Outgoing Email Settings", "mail_server") or "").strip(): - frappe.db.set_value("Outgoing Email Settings", "Outgoing Email Settings", "enabled", 1) diff --git a/frappe/patches/v4_1/enable_print_as_pdf.py b/frappe/patches/v4_1/enable_print_as_pdf.py deleted file mode 100644 index 74db9f72ca..0000000000 --- a/frappe/patches/v4_1/enable_print_as_pdf.py +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "print_settings") - print_settings = frappe.get_doc("Print Settings") - print_settings.print_style = "Modern" - - try: - import pdfkit - except ImportError: - pass - else: - # if someone has already configured in Outgoing Email Settings - outgoing_email_settings = frappe.db.get_singles_dict("Outgoing Email Settings") - if "send_print_as_pdf" in outgoing_email_settings: - print_settings.send_print_as_pdf = outgoing_email_settings.send_print_as_pdf - print_settings.pdf_page_size = outgoing_email_settings.pdf_page_size - - else: - print_settings.send_print_as_pdf = 1 - - print_settings.save() diff --git a/frappe/patches/v4_1/file_manager_fix.py b/frappe/patches/v4_1/file_manager_fix.py deleted file mode 100644 index cd30c94177..0000000000 --- a/frappe/patches/v4_1/file_manager_fix.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals, print_function - -import frappe -import os -from frappe.core.doctype.file.file import get_content_hash, get_file_name -from frappe.utils import get_files_path, get_site_path - -# The files missed by the previous patch might have been replaced with new files -# with the same filename -# -# This patch does the following, -# * Detect which files were replaced and rename them with name{hash:5}.extn and -# update filedata record for the new file -# -# * make missing_files.txt in site dir with files that should be recovered from -# a backup from a time before version 3 migration -# -# * Patch remaining unpatched File records. -from six import iteritems - - -def execute(): - frappe.db.auto_commit_on_many_writes = True - rename_replacing_files() - for name, file_name, file_url in frappe.db.sql( - """select name, file_name, file_url from `tabFile` - where ifnull(file_name, '')!='' and ifnull(content_hash, '')=''"""): - b = frappe.get_doc('File', name) - old_file_name = b.file_name - b.file_name = os.path.basename(old_file_name) - if old_file_name.startswith('files/') or old_file_name.startswith('/files/'): - b.file_url = os.path.normpath('/' + old_file_name) - else: - b.file_url = os.path.normpath('/files/' + old_file_name) - try: - _file = frappe.get_doc("File", {"file_name": name}) - content = _file.get_content() - b.content_hash = get_content_hash(content) - except IOError: - print('Warning: Error processing ', name) - b.content_hash = None - b.flags.ignore_duplicate_entry_error = True - b.save() - frappe.db.auto_commit_on_many_writes = False - -def get_replaced_files(): - ret = [] - new_files = dict(frappe.db.sql("select name, file_name from `tabFile` where file_name not like 'files/%'")) - old_files = dict(frappe.db.sql("select name, file_name from `tabFile` where ifnull(content_hash, '')=''")) - invfiles = invert_dict(new_files) - - for nname, nfilename in iteritems(new_files): - if 'files/' + nfilename in old_files.values(): - ret.append((nfilename, invfiles[nfilename])) - return ret - -def rename_replacing_files(): - replaced_files = get_replaced_files() - if len(replaced_files): - missing_files = [v[0] for v in replaced_files] - with open(get_site_path('missing_files.txt'), 'w') as f: - f.write(('\n'.join(missing_files) + '\n').encode('utf-8')) - - for file_name, file_datas in replaced_files: - print ('processing ' + file_name) - content_hash = frappe.db.get_value('File', file_datas[0], 'content_hash') - if not content_hash: - continue - new_file_name = get_file_name(file_name, content_hash) - if os.path.exists(get_files_path(new_file_name)): - continue - print('skipping ' + file_name) - try: - os.rename(get_files_path(file_name), get_files_path(new_file_name)) - except OSError: - print('Error renaming ', file_name) - for name in file_datas: - f = frappe.get_doc('File', name) - f.file_name = new_file_name - f.file_url = '/files/' + new_file_name - f.save() - -def invert_dict(ddict): - ret = {} - for k,v in iteritems(ddict): - if not ret.get(v): - ret[v] = [k] - else: - ret[v].append(k) - return ret - -def get_file_name(fname, hash): - if '.' in fname: - partial, extn = fname.rsplit('.', 1) - else: - partial = fname - extn = '' - return '{partial}{suffix}.{extn}'.format(partial=partial, extn=extn, suffix=hash[:5]) diff --git a/frappe/patches/v4_2/print_with_letterhead.py b/frappe/patches/v4_2/print_with_letterhead.py deleted file mode 100644 index 3e611ce073..0000000000 --- a/frappe/patches/v4_2/print_with_letterhead.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "print_settings") - print_settings = frappe.get_doc("Print Settings") - print_settings.with_letterhead = 1 - print_settings.save() diff --git a/frappe/patches/v4_2/refactor_website_routing.py b/frappe/patches/v4_2/refactor_website_routing.py deleted file mode 100644 index a5856db1c9..0000000000 --- a/frappe/patches/v4_2/refactor_website_routing.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - # clear all static web pages - frappe.delete_doc("DocType", "Website Route", force=1) - frappe.delete_doc("Page", "sitemap-browser", force=1) - frappe.db.sql("drop table if exists `tabWebsite Route`") diff --git a/frappe/patches/v4_2/set_assign_in_doc.py b/frappe/patches/v4_2/set_assign_in_doc.py deleted file mode 100644 index a6a06492a0..0000000000 --- a/frappe/patches/v4_2/set_assign_in_doc.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for name in frappe.db.sql_list("""select name from `tabToDo` - where ifnull(reference_type, '')!='' and ifnull(reference_name, '')!=''"""): - try: - frappe.get_doc("ToDo", name).on_update() - except Exception as e: - if not frappe.db.is_table_missing(e): - raise diff --git a/frappe/patches/v4_3/remove_allow_on_submit_customization.py b/frappe/patches/v4_3/remove_allow_on_submit_customization.py deleted file mode 100644 index af6ade68e6..0000000000 --- a/frappe/patches/v4_3/remove_allow_on_submit_customization.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for d in frappe.get_all("Property Setter", fields=["name", "doc_type"], - filters={"doctype_or_field": "DocField", "property": "allow_on_submit", "value": "1"}): - frappe.delete_doc("Property Setter", d.name) - frappe.clear_cache(doctype=d.doc_type) diff --git a/frappe/patches/v5_0/bookmarks_to_stars.py b/frappe/patches/v5_0/bookmarks_to_stars.py deleted file mode 100644 index 048d059701..0000000000 --- a/frappe/patches/v5_0/bookmarks_to_stars.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import unicode_literals -import json -import frappe -import frappe.defaults -from frappe.desk.like import _toggle_like -from six import string_types - -def execute(): - for user in frappe.get_all("User"): - username = user["name"] - bookmarks = frappe.db.get_default("_bookmarks", username) - - if not bookmarks: - continue - - if isinstance(bookmarks, string_types): - bookmarks = json.loads(bookmarks) - - for opts in bookmarks: - route = (opts.get("route") or "").strip("#/ ") - - if route and route.startswith("Form"): - try: - view, doctype, docname = opts["route"].split("/") - except ValueError: - continue - - if frappe.db.exists(doctype, docname): - if (doctype=="DocType" - or int(frappe.db.get_value("DocType", doctype, "issingle") or 0) - or not frappe.db.table_exists(doctype)): - continue - _toggle_like(doctype, docname, add="Yes", user=username) diff --git a/frappe/patches/v5_0/clear_website_group_and_notifications.py b/frappe/patches/v5_0/clear_website_group_and_notifications.py deleted file mode 100644 index bad50222a3..0000000000 --- a/frappe/patches/v5_0/clear_website_group_and_notifications.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.delete_doc("DocType", "Post") - frappe.delete_doc("DocType", "Website Group") - frappe.delete_doc("DocType", "Website Route Permission") - frappe.delete_doc("DocType", "User Vote") - frappe.delete_doc("DocType", "Notification Count") diff --git a/frappe/patches/v5_0/communication_parent.py b/frappe/patches/v5_0/communication_parent.py deleted file mode 100644 index 2ea3b401c6..0000000000 --- a/frappe/patches/v5_0/communication_parent.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "communication") - frappe.db.sql("""update tabCommunication set reference_doctype = parenttype, reference_name = parent""") diff --git a/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py b/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py deleted file mode 100644 index 0ea2ee2387..0000000000 --- a/frappe/patches/v5_0/convert_to_barracuda_and_utf8mb4.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.database.mariadb.setup_db import check_database_settings -from frappe.model.meta import trim_tables - -def execute(): - check_database_settings() - - for table in frappe.db.get_tables(): - frappe.db.sql_ddl("""alter table `{0}` ENGINE=InnoDB ROW_FORMAT=COMPRESSED""".format(table)) - try: - frappe.db.sql_ddl("""alter table `{0}` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci""".format(table)) - except: - # if row size gets too large, let it be old charset! - pass - diff --git a/frappe/patches/v5_0/expire_old_scheduler_logs.py b/frappe/patches/v5_0/expire_old_scheduler_logs.py deleted file mode 100644 index 8b65ed5fb1..0000000000 --- a/frappe/patches/v5_0/expire_old_scheduler_logs.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Error Log") - - from frappe.core.doctype.error_log.error_log import set_old_logs_as_seen - set_old_logs_as_seen() diff --git a/frappe/patches/v5_0/fix_email_alert.py b/frappe/patches/v5_0/fix_email_alert.py deleted file mode 100644 index 0676f50a9c..0000000000 --- a/frappe/patches/v5_0/fix_email_alert.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -def execute(): - frappe.reload_doctype("Notification") - for e in frappe.get_all("Notification"): - notification = frappe.get_doc("Notification", e.name) - if notification.event == "Date Change": - if notification.days_in_advance < 0: - notification.event = "Days After" - notification.days_in_advance = -email_alert.days_in_advance - else: - notification.event = "Days Before" - - notification.save() diff --git a/frappe/patches/v5_0/fix_null_date_datetime.py b/frappe/patches/v5_0/fix_null_date_datetime.py deleted file mode 100644 index e4f4e9e8b9..0000000000 --- a/frappe/patches/v5_0/fix_null_date_datetime.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for table in frappe.db.get_tables(): - changed = False - desc = frappe.db.sql("desc `{table}`".format(table=table), as_dict=True) - for field in desc: - if field["Type"] == "date": - frappe.db.sql("""update `{table}` set `{fieldname}`=null where `{fieldname}`='0000-00-00'""".format( - table=table, fieldname=field["Field"])) - changed = True - - elif field["Type"] == "datetime(6)": - frappe.db.sql("""update `{table}` set `{fieldname}`=null where `{fieldname}`='0000-00-00 00:00:00.000000'""".format( - table=table, fieldname=field["Field"])) - changed = True - - if changed: - frappe.db.commit() diff --git a/frappe/patches/v5_0/fix_text_editor_file_urls.py b/frappe/patches/v5_0/fix_text_editor_file_urls.py deleted file mode 100644 index a6d7d2fb9a..0000000000 --- a/frappe/patches/v5_0/fix_text_editor_file_urls.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import unicode_literals, print_function -import frappe -import re - -def execute(): - """Fix relative urls for image src="files/" to src="/files/" in DocTypes with text editor fields""" - doctypes_with_text_fields = frappe.get_all("DocField", fields=["parent", "fieldname"], - filters={"fieldtype": "Text Editor"}) - - done = [] - for opts in doctypes_with_text_fields: - if opts in done: - continue - - try: - result = frappe.get_all(opts.parent, fields=["name", opts.fieldname]) - except frappe.db.SQLError: - # bypass single tables - continue - - for data in result: - old_value = data[opts.fieldname] - if not old_value: - continue - - html = scrub_relative_urls(old_value) - if html != old_value: - # print_diff(html, old_value) - frappe.db.set_value(opts.parent, data.name, opts.fieldname, html, update_modified=False) - - done.append(opts) - -def scrub_relative_urls(html): - """prepend a slash before a relative url""" - try: - return re.sub(r'src[\s]*=[\s]*[\'"]files/([^\'"]*)[\'"]', r'src="/files/\g<1>"', html) - except: - print("Error", html) - raise - -def print_diff(html, old_value): - import difflib - diff = difflib.unified_diff(old_value.splitlines(1), html.splitlines(1), lineterm='') - print('\n'.join(list(diff))) diff --git a/frappe/patches/v5_0/force_sync_website.py b/frappe/patches/v5_0/force_sync_website.py deleted file mode 100644 index 5dcd0d79c7..0000000000 --- a/frappe/patches/v5_0/force_sync_website.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - pass diff --git a/frappe/patches/v5_0/modify_session.py b/frappe/patches/v5_0/modify_session.py deleted file mode 100644 index f0e247a633..0000000000 --- a/frappe/patches/v5_0/modify_session.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if "device" not in frappe.db.get_table_columns("Sessions"): - frappe.db.sql("alter table tabSessions add column `device` varchar(255) default 'desktop'") diff --git a/frappe/patches/v5_0/move_scheduler_last_event_to_system_settings.py b/frappe/patches/v5_0/move_scheduler_last_event_to_system_settings.py deleted file mode 100644 index 0fa1dad1e5..0000000000 --- a/frappe/patches/v5_0/move_scheduler_last_event_to_system_settings.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('System Settings') - last = frappe.db.get_global('scheduler_last_event') - frappe.db.set_value('System Settings', 'System Settings', 'scheduler_last_event', last) - diff --git a/frappe/patches/v5_0/remove_shopping_cart_app.py b/frappe/patches/v5_0/remove_shopping_cart_app.py deleted file mode 100644 index babde585a1..0000000000 --- a/frappe/patches/v5_0/remove_shopping_cart_app.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -def execute(): - from frappe.installer import remove_from_installed_apps - remove_from_installed_apps("shopping_cart") diff --git a/frappe/patches/v5_0/rename_ref_type_fieldnames.py b/frappe/patches/v5_0/rename_ref_type_fieldnames.py deleted file mode 100644 index dd24f6e5b5..0000000000 --- a/frappe/patches/v5_0/rename_ref_type_fieldnames.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - try: - frappe.db.sql("alter table `tabEmail Queue` change `ref_docname` `reference_name` varchar(255)") - except Exception as e: - if not frappe.db.is_table_or_column_missing(e): - raise - - try: - frappe.db.sql("alter table `tabEmail Queue` change `ref_doctype` `reference_doctype` varchar(255)") - except Exception as e: - if not frappe.db.is_table_or_column_missing(e): - raise - frappe.reload_doctype("Email Queue") diff --git a/frappe/patches/v5_0/rename_table_fieldnames.py b/frappe/patches/v5_0/rename_table_fieldnames.py deleted file mode 100644 index b716599f28..0000000000 --- a/frappe/patches/v5_0/rename_table_fieldnames.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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.model.utils.rename_field import rename_field -from frappe.modules import scrub, get_doctype_module - -rename_map = { - "Customize Form": [ - ["customize_form_fields", "fields"] - ], - "Email Alert": [ - ["email_alert_recipients", "recipients"] - ], - "Workflow": [ - ["workflow_document_states", "states"], - ["workflow_transitions", "transitions"] - ] -} - -def execute(): - frappe.reload_doc("custom", "doctype", "customize_form") - frappe.reload_doc("email", "doctype", "notification") - frappe.reload_doc("desk", "doctype", "event") - frappe.reload_doc("workflow", "doctype", "workflow") - - for dt, field_list in rename_map.items(): - for field in field_list: - rename_field(dt, field[0], field[1]) diff --git a/frappe/patches/v5_0/style_settings_to_website_theme.py b/frappe/patches/v5_0/style_settings_to_website_theme.py deleted file mode 100644 index 40414d4e20..0000000000 --- a/frappe/patches/v5_0/style_settings_to_website_theme.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils import cint - -def execute(): - frappe.reload_doc("website", "doctype", "website_theme") - frappe.reload_doc("website", "website_theme", "standard") - frappe.reload_doctype("Website Settings") - migrate_style_settings() - frappe.delete_doc("website", "doctype", "style_settings") - -def migrate_style_settings(): - style_settings = frappe.db.get_singles_dict("Style Settings") - standard_website_theme = frappe.get_doc("Website Theme", "Standard") - - website_theme = frappe.copy_doc(standard_website_theme) - website_theme.custom = 1 - website_theme.theme = _("Custom") - - if style_settings: - map_color_fields(style_settings, website_theme) - map_other_fields(style_settings, website_theme) - - website_theme.no_sidebar = cint(frappe.db.get_single_value("Website Settings", "no_sidebar")) - - website_theme.save() - website_theme.set_as_default() - -def map_color_fields(style_settings, website_theme): - color_fields_map = { - "page_text": "text_color", - "page_links": "link_color", - "top_bar_background": "top_bar_color", - "top_bar_foreground": "top_bar_text_color", - "footer_background": "footer_color", - "footer_color": "footer_text_color", - } - - for from_fieldname, to_fieldname in color_fields_map.items(): - from_value = style_settings.get(from_fieldname) - - if from_value: - website_theme.set(to_fieldname, "#{0}".format(from_value)) - -def map_other_fields(style_settings, website_theme): - other_fields_map = { - "heading_text_as": "heading_style", - "google_web_font_for_heading": "heading_webfont", - "google_web_font_for_text": "text_webfont", - "add_css": "css" - } - - for from_fieldname, to_fieldname in other_fields_map.items(): - website_theme.set(to_fieldname, style_settings.get(from_fieldname)) - - for fieldname in ("apply_style", "background_image", "background_color", - "font_size"): - website_theme.set(fieldname, style_settings.get(fieldname)) diff --git a/frappe/patches/v5_0/update_shared.py b/frappe/patches/v5_0/update_shared.py deleted file mode 100644 index f2b77895d8..0000000000 --- a/frappe/patches/v5_0/update_shared.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import unicode_literals -import frappe -import frappe.share - -def execute(): - frappe.reload_doc("core", "doctype", "docperm") - frappe.reload_doc("core", "doctype", "docshare") - frappe.reload_doc('email', 'doctype', 'email_account') - - # default share to all writes - frappe.db.sql("""update tabDocPerm set `share`=1 where ifnull(`write`,0)=1 and ifnull(`permlevel`,0)=0""") - - # every user must have access to his / her own detail - users = frappe.get_all("User", filters={"user_type": "System User"}) - usernames = [user.name for user in users] - for user in usernames: - frappe.share.add("User", user, user, write=1, share=1) - - # move event user to shared - if frappe.db.exists("DocType", "Event User"): - for event in frappe.get_all("Event User", fields=["parent", "person"]): - if event.person in usernames: - if not frappe.db.exists("Event", event.parent): - frappe.db.sql("delete from `tabEvent User` where parent = %s",event.parent) - else: - frappe.share.add("Event", event.parent, event.person, write=1) - - frappe.delete_doc("DocType", "Event User") - - # move note user to shared - if frappe.db.exists("DocType", "Note User"): - for note in frappe.get_all("Note User", fields=["parent", "user", "permission"]): - perm = {"read": 1} if note.permission=="Read" else {"write": 1} - if note.user in usernames: - frappe.share.add("Note", note.parent, note.user, **perm) - - frappe.delete_doc("DocType", "Note User") diff --git a/frappe/patches/v5_0/v4_to_v5.py b/frappe/patches/v5_0/v4_to_v5.py deleted file mode 100644 index cd34f04c97..0000000000 --- a/frappe/patches/v5_0/v4_to_v5.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - changed = ( - ("desk", ("feed", "event", "todo", "note")), - ("custom", ("custom_field", "custom_script", "customize_form", - "customize_form_field", "property_setter")), - ("email", ("email_queue", "notification", "notification_recipient", "standard_reply")), - ("geo", ("country", "currency")), - ("print", ("letter_head", "print_format", "print_settings")) - ) - for module in changed: - for doctype in module[1]: - frappe.reload_doc(module[0], "doctype", doctype) diff --git a/frappe/patches/v5_2/change_checks_to_not_null.py b/frappe/patches/v5_2/change_checks_to_not_null.py deleted file mode 100644 index 23f5d659b5..0000000000 --- a/frappe/patches/v5_2/change_checks_to_not_null.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import cint -from frappe.model import default_fields - -def execute(): - for table in frappe.db.get_tables(): - doctype = table[3:] - if frappe.db.exists("DocType", doctype): - fieldnames = [df["fieldname"] for df in - frappe.get_all("DocField", fields=["fieldname"], filters={"parent": doctype})] - custom_fieldnames = [df["fieldname"] for df in - frappe.get_all("Custom Field", fields=["fieldname"], filters={"dt": doctype})] - - else: - fieldnames = custom_fieldnames = [] - - for column in frappe.db.sql("""desc `{0}`""".format(table), as_dict=True): - if column["Type"]=="int(1)": - fieldname = column["Field"] - - # only change for defined fields, ignore old fields that don't exist in meta - if not (fieldname in default_fields or fieldname in fieldnames or fieldname in custom_fieldnames): - continue - - # set 0 - frappe.db.sql("""update `{table}` set `{column}`=0 where `{column}` is null"""\ - .format(table=table, column=fieldname)) - frappe.db.commit() - - # change definition - frappe.db.sql_ddl("""alter table `{table}` - modify `{column}` int(1) not null default {default}"""\ - .format(table=table, column=fieldname, default=cint(column["Default"]))) diff --git a/frappe/patches/v5_3/__init__.py b/frappe/patches/v5_3/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v5_3/rename_chinese_languages.py b/frappe/patches/v5_3/rename_chinese_languages.py deleted file mode 100644 index 8bc954c04c..0000000000 --- a/frappe/patches/v5_3/rename_chinese_languages.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe -from frappe.translate import rename_language - -def execute(): - language_map = { - "中国(简体)": "簡體中文", - "中國(繁體)": "正體中文" - } - - for old_name, new_name in language_map.items(): - rename_language(old_name, new_name) diff --git a/frappe/patches/v6_0/__init__.py b/frappe/patches/v6_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_0/communication_status_and_permission.py b/frappe/patches/v6_0/communication_status_and_permission.py deleted file mode 100644 index c68ed9b4d6..0000000000 --- a/frappe/patches/v6_0/communication_status_and_permission.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.permissions import reset_perms - -def execute(): - frappe.reload_doctype("Communication") - - # set status = "Linked" - frappe.db.sql("""update `tabCommunication` set status='Linked' - where ifnull(reference_doctype, '')!='' and ifnull(reference_name, '')!=''""") - - frappe.db.sql("""update `tabCommunication` set status='Closed' - where status='Archived'""") - - # reset permissions if owner of all DocPerms is Administrator - if not frappe.db.sql("""select name from `tabDocPerm` - where parent='Communication' and ifnull(owner, '')!='Administrator'"""): - - reset_perms("Communication") diff --git a/frappe/patches/v6_0/document_type_rename.py b/frappe/patches/v6_0/document_type_rename.py deleted file mode 100644 index 16c7d34286..0000000000 --- a/frappe/patches/v6_0/document_type_rename.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""update tabDocType set document_type='Document' - where document_type='Transaction'""") - frappe.db.sql("""update tabDocType set document_type='Setup' - where document_type='Master'""") diff --git a/frappe/patches/v6_0/fix_ghana_currency.py b/frappe/patches/v6_0/fix_ghana_currency.py deleted file mode 100644 index 67f740d240..0000000000 --- a/frappe/patches/v6_0/fix_ghana_currency.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals - -def execute(): - from frappe.geo.country_info import get_all - import frappe.utils.install - - countries = get_all() - frappe.utils.install.add_country_and_currency("Ghana", frappe._dict(countries["Ghana"])) diff --git a/frappe/patches/v6_0/make_task_log_folder.py b/frappe/patches/v6_0/make_task_log_folder.py deleted file mode 100644 index 87d6e4126f..0000000000 --- a/frappe/patches/v6_0/make_task_log_folder.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe.utils, os - -def execute(): - path = frappe.utils.get_site_path('task-logs') - if not os.path.exists(path): - os.makedirs(path) diff --git a/frappe/patches/v6_1/__init__.py b/frappe/patches/v6_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_1/rename_file_data.py b/frappe/patches/v6_1/rename_file_data.py deleted file mode 100644 index 83152271eb..0000000000 --- a/frappe/patches/v6_1/rename_file_data.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import print_function, unicode_literals -import frappe - -def execute(): - from frappe.core.doctype.file.file import make_home_folder - - if not frappe.db.exists("DocType", "File"): - frappe.rename_doc("DocType", "File Data", "File") - frappe.reload_doctype("File") - - if not frappe.db.exists("File", {"is_home_folder": 1}): - make_home_folder() - - # make missing folders and set parent folder - for file in frappe.get_all("File", filters={"is_folder": 0}): - file = frappe.get_doc("File", file.name) - file.flags.ignore_folder_validate = True - file.flags.ignore_file_validate = True - file.flags.ignore_duplicate_entry_error = True - file.flags.ignore_links = True - file.set_folder_name() - try: - file.save() - except: - print(frappe.get_traceback()) - raise - - from frappe.utils.nestedset import rebuild_tree - rebuild_tree("File", "folder") - - # reset file size - for folder in frappe.db.sql("""select name from tabFile f1 where is_folder = 1 and - (select count(*) from tabFile f2 where f2.folder = f1.name and f2.is_folder = 1) = 0"""): - folder = frappe.get_doc("File", folder[0]) - folder.save() - - - diff --git a/frappe/patches/v6_11/__init__.py b/frappe/patches/v6_11/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_11/rename_field_in_email_account.py b/frappe/patches/v6_11/rename_field_in_email_account.py deleted file mode 100644 index 319b569802..0000000000 --- a/frappe/patches/v6_11/rename_field_in_email_account.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("email", "doctype", "email_account") - if frappe.db.has_column('Email Account', 'pop3_server'): - frappe.db.sql("update `tabEmail Account` set email_server = pop3_server") diff --git a/frappe/patches/v6_15/__init__.py b/frappe/patches/v6_15/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_15/remove_property_setter_for_previous_field.py b/frappe/patches/v6_15/remove_property_setter_for_previous_field.py deleted file mode 100644 index b24bf38442..0000000000 --- a/frappe/patches/v6_15/remove_property_setter_for_previous_field.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe, json -from frappe.utils import cstr - -def execute(): - # deprecated on 2016-03-09 - # using insert_after instead - return - - frappe.db.sql("""delete from `tabProperty Setter` where property='previous_field'""") - - all_custom_fields = frappe._dict() - for d in frappe.db.sql("""select name, dt, fieldname, insert_after from `tabCustom Field` - where insert_after is not null and insert_after != ''""", as_dict=1): - all_custom_fields.setdefault(d.dt, frappe._dict()).setdefault(d.fieldname, d.insert_after) - - for dt, custom_fields in all_custom_fields.items(): - _idx = [] - existing_ps = frappe.db.get_value("Property Setter", - {"doc_type": dt, "property": "_idx"}, ["name", "value", "creation"], as_dict=1) - - # if no existsing property setter, build based on meta - if not existing_ps: - _idx = get_sorted_fields(dt, custom_fields) - else: - _idx = json.loads(existing_ps.value) - - idx_needs_to_be_fixed = False - for fieldname, insert_after in custom_fields.items(): - # Delete existing property setter if field is not there - if fieldname not in _idx: - idx_needs_to_be_fixed = True - break - else: - previous_field = _idx[_idx.index(fieldname) - 1] - - if previous_field != insert_after and cstr(existing_ps.creation) >= "2015-12-28": - idx_needs_to_be_fixed = True - break - - if idx_needs_to_be_fixed: - frappe.delete_doc("Property Setter", existing_ps.name) - _idx = get_sorted_fields(dt, custom_fields) - - if _idx: - frappe.make_property_setter({ - "doctype":dt, - "doctype_or_field": "DocType", - "property": "_idx", - "value": json.dumps(_idx), - "property_type": "Text" - }, validate_fields_for_doctype=False) - - -def get_sorted_fields(doctype, custom_fields): - """sort on basis of insert_after""" - fields_dict = frappe.get_meta(doctype).get("fields") - - standard_fields_count = frappe.db.sql("""select count(name) from `tabDocField` - where parent=%s""", doctype)[0][0] - - newlist = [] - pending = [d.fieldname for d in fields_dict] - - maxloops = len(custom_fields) + 20 - while (pending and maxloops>0): - maxloops -= 1 - for fieldname in pending[:]: - if fieldname in custom_fields and len(newlist) >= standard_fields_count: - # field already added - for n in newlist: - if n==custom_fields.get(fieldname): - newlist.insert(newlist.index(n)+1, fieldname) - pending.remove(fieldname) - break - else: - newlist.append(fieldname) - pending.remove(fieldname) - - # recurring at end - if pending: - newlist += pending - - return newlist diff --git a/frappe/patches/v6_15/set_username.py b/frappe/patches/v6_15/set_username.py deleted file mode 100644 index 513ff3301d..0000000000 --- a/frappe/patches/v6_15/set_username.py +++ /dev/null @@ -1,16 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("User") - - # give preference to System Users - users = frappe.db.sql_list("""select name from `tabUser` order by if(user_type='System User', 0, 1)""") - for name in users: - user = frappe.get_doc("User", name) - if user.username or not user.first_name: - continue - - username = user.suggest_username() - if username: - user.db_set("username", username, update_modified=False) diff --git a/frappe/patches/v6_16/__init__.py b/frappe/patches/v6_16/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_16/feed_doc_owner.py b/frappe/patches/v6_16/feed_doc_owner.py deleted file mode 100644 index 2dac9a143d..0000000000 --- a/frappe/patches/v6_16/feed_doc_owner.py +++ /dev/null @@ -1,31 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("Communication") - - for doctype, name in frappe.db.sql("""select distinct reference_doctype, reference_name - from `tabCommunication` - where - (reference_doctype is not null and reference_doctype != '') - and (reference_name is not null and reference_name != '') - and (reference_owner is null or reference_owner = '') - for update"""): - - owner = frappe.db.get_value(doctype, name, "owner") - - if not owner: - continue - - frappe.db.sql("""update `tabCommunication` - set reference_owner=%(owner)s - where - reference_doctype=%(doctype)s - and reference_name=%(name)s - and (reference_owner is null or reference_owner = '')""".format(doctype=doctype), { - "doctype": doctype, - "name": name, - "owner": owner - }) - - frappe.db.commit() diff --git a/frappe/patches/v6_16/star_to_like.py b/frappe/patches/v6_16/star_to_like.py deleted file mode 100644 index e859223d54..0000000000 --- a/frappe/patches/v6_16/star_to_like.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.database.schema import add_column - -def execute(): - frappe.db.sql("""update `tabSingles` set field='_liked_by' where field='_starred_by'""") - frappe.db.commit() - - for table in frappe.db.get_tables(): - columns = [r[0] for r in frappe.db.sql("DESC `{0}`".format(table))] - if "_starred_by" in columns and '_liked_by' not in columns: - frappe.db.sql_ddl("""alter table `{0}` change `_starred_by` `_liked_by` Text """.format(table)) - - if not frappe.db.has_column("Communication", "_liked_by"): - add_column("Communication", "_liked_by", "Text") diff --git a/frappe/patches/v6_19/__init__.py b/frappe/patches/v6_19/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_19/comment_feed_communication.py b/frappe/patches/v6_19/comment_feed_communication.py deleted file mode 100644 index a7503c08ab..0000000000 --- a/frappe/patches/v6_19/comment_feed_communication.py +++ /dev/null @@ -1,307 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.model.rename_doc import get_link_fields -from frappe.model.dynamic_links import dynamic_link_queries -from frappe.permissions import reset_perms - -def execute(): - # comments stay comments in v12 - return - - frappe.reload_doctype("DocType") - frappe.reload_doctype("Communication") - reset_perms("Communication") - - migrate_comments() - frappe.delete_doc("DocType", "Comment") - # frappe.db.sql_ddl("drop table `tabComment`") - - migrate_feed() - frappe.delete_doc("DocType", "Feed") - # frappe.db.sql_ddl("drop table `tabFeed`") - - update_timeline_doc_for("Blogger") - -def migrate_comments(): - from_fields = "" - to_fields = "" - - if "reference_doctype" in frappe.db.get_table_columns("Comment"): - from_fields = "reference_doctype as link_doctype, reference_name as link_name," - to_fields = "link_doctype, link_name," - - # comments - frappe.db.sql("""insert ignore into `tabCommunication` ( - subject, - content, - sender, - sender_full_name, - comment_type, - communication_date, - reference_doctype, - reference_name, - {to_fields} - - name, - user, - owner, - creation, - modified_by, - modified, - status, - sent_or_received, - communication_type, - seen - ) - select - substring(comment, 1, 100) as subject, - comment as content, - comment_by as sender, - comment_by_fullname as sender_full_name, - comment_type, - ifnull(timestamp(comment_date, comment_time), creation) as communication_date, - comment_doctype as reference_doctype, - comment_docname as reference_name, - {from_fields} - - name, - owner as user, - owner, - creation, - modified_by, - modified, - 'Linked' as status, - 'Sent' as sent_or_received, - 'Comment' as communication_type, - 1 as seen - from `tabComment` where comment_doctype is not null and comment_doctype not in ('Message', 'My Company')""" - .format(to_fields=to_fields, from_fields=from_fields)) - - # chat and assignment notifications - frappe.db.sql("""insert ignore into `tabCommunication` ( - subject, - content, - sender, - sender_full_name, - comment_type, - communication_date, - reference_doctype, - reference_name, - {to_fields} - - name, - user, - owner, - creation, - modified_by, - modified, - status, - sent_or_received, - communication_type, - seen - ) - select - case - when parenttype='Assignment' then %(assignment)s - else substring(comment, 1, 100) - end - as subject, - comment as content, - comment_by as sender, - comment_by_fullname as sender_full_name, - comment_type, - ifnull(timestamp(comment_date, comment_time), creation) as communication_date, - 'User' as reference_doctype, - comment_docname as reference_name, - {from_fields} - - name, - owner as user, - owner, - creation, - modified_by, - modified, - 'Linked' as status, - 'Sent' as sent_or_received, - case - when parenttype='Assignment' then 'Notification' - else 'Chat' - end - as communication_type, - 1 as seen - from `tabComment` where comment_doctype in ('Message', 'My Company')""" - .format(to_fields=to_fields, from_fields=from_fields), {"assignment": _("Assignment")}) - -def migrate_feed(): - # migrate delete feed - for doctype in frappe.db.sql("""select distinct doc_type from `tabFeed` where subject=%(deleted)s""", {"deleted": _("Deleted")}): - frappe.db.sql("""insert ignore into `tabCommunication` ( - subject, - sender, - sender_full_name, - comment_type, - communication_date, - reference_doctype, - - name, - user, - owner, - creation, - modified_by, - modified, - status, - sent_or_received, - communication_type, - seen - ) - select - concat_ws(" ", %(_doctype)s, doc_name) as subject, - owner as sender, - full_name as sender_full_name, - 'Deleted' as comment_type, - creation as communication_date, - doc_type as reference_doctype, - - name, - owner as user, - owner, - creation, - modified_by, - modified, - 'Linked' as status, - 'Sent' as sent_or_received, - 'Comment' as communication_type, - 1 as seen - from `tabFeed` where subject=%(deleted)s and doc_type=%(doctype)s""", { - "deleted": _("Deleted"), - "doctype": doctype, - "_doctype": _(doctype) - }) - - # migrate feed type login or empty - frappe.db.sql("""insert ignore into `tabCommunication` ( - subject, - sender, - sender_full_name, - comment_type, - communication_date, - reference_doctype, - reference_name, - - name, - user, - owner, - creation, - modified_by, - modified, - status, - sent_or_received, - communication_type, - seen - ) - select - subject, - owner as sender, - full_name as sender_full_name, - case - when feed_type='Login' then 'Info' - else 'Updated' - end as comment_type, - creation as communication_date, - doc_type as reference_doctype, - doc_name as reference_name, - - name, - owner as user, - owner, - creation, - modified_by, - modified, - 'Linked' as status, - 'Sent' as sent_or_received, - 'Comment' as communication_type, - 1 as seen - from `tabFeed` where (feed_type in ('Login', '') or feed_type is null)""") - -def update_timeline_doc_for(timeline_doctype): - """NOTE: This method may be used by other apps for patching. It also has COMMIT after each update.""" - - # find linked doctypes - # link fields - update_for_linked_docs(timeline_doctype) - - # dynamic link fields - update_for_dynamically_linked_docs(timeline_doctype) - -def update_for_linked_docs(timeline_doctype): - for df in get_link_fields(timeline_doctype): - if df.issingle: - continue - - reference_doctype = df.parent - - if not is_valid_timeline_doctype(reference_doctype, timeline_doctype): - continue - - for doc in frappe.get_all(reference_doctype, fields=["name", df.fieldname]): - timeline_name = doc.get(df.fieldname) - update_communication(timeline_doctype, timeline_name, reference_doctype, doc.name) - -def update_for_dynamically_linked_docs(timeline_doctype): - dynamic_link_fields = [] - for query in dynamic_link_queries: - for df in frappe.db.sql(query, as_dict=True): - dynamic_link_fields.append(df) - - for df in dynamic_link_fields: - reference_doctype = df.parent - - if not is_valid_timeline_doctype(reference_doctype, timeline_doctype): - continue - - try: - docs = frappe.get_all(reference_doctype, fields=["name", df.fieldname], - filters={ df.options: timeline_doctype }) - except frappe.db.SQLError as e: - if frappe.db.is_table_missing(e): - # single - continue - else: - raise - - for doc in docs: - timeline_name = doc.get(df.fieldname) - update_communication(timeline_doctype, timeline_name, reference_doctype, doc.name) - -def update_communication(timeline_doctype, timeline_name, reference_doctype, reference_name): - if not timeline_name: - return - - frappe.db.sql("""update `tabCommunication` set timeline_doctype=%(timeline_doctype)s, timeline_name=%(timeline_name)s - where (reference_doctype=%(reference_doctype)s and reference_name=%(reference_name)s) - and (timeline_doctype is null or timeline_doctype='') - and (timeline_name is null or timeline_name='')""", { - "timeline_doctype": timeline_doctype, - "timeline_name": timeline_name, - "reference_doctype": reference_doctype, - "reference_name": reference_name - }) - - frappe.db.commit() - -def is_valid_timeline_doctype(reference_doctype, timeline_doctype): - # for reloading timeline_field - frappe.reload_doctype(reference_doctype) - - # make sure the timeline field's doctype is same as timeline doctype - meta = frappe.get_meta(reference_doctype) - if not meta.timeline_field: - return False - - doctype = meta.get_link_doctype(meta.timeline_field) - if doctype != timeline_doctype: - return False - - - return True diff --git a/frappe/patches/v6_2/__init__.py b/frappe/patches/v6_2/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_2/ignore_user_permissions_if_missing.py b/frappe/patches/v6_2/ignore_user_permissions_if_missing.py deleted file mode 100644 index 356d28989a..0000000000 --- a/frappe/patches/v6_2/ignore_user_permissions_if_missing.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("System Settings") - system_settings = frappe.get_doc("System Settings") - system_settings.flags.ignore_mandatory = 1 - system_settings.save() diff --git a/frappe/patches/v6_2/rename_backup_manager.py b/frappe/patches/v6_2/rename_backup_manager.py deleted file mode 100644 index af02e55878..0000000000 --- a/frappe/patches/v6_2/rename_backup_manager.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - unset = False - frappe.reload_doc("integrations", "doctype", "dropbox_backup") - - dropbox_backup = frappe.get_doc("Dropbox Backup", "Dropbox Backup") - for df in dropbox_backup.meta.fields: - value = frappe.db.get_single_value("Backup Manager", df.fieldname) - if value: - if df.fieldname=="upload_backups_to_dropbox" and value=="Never": - value = "Daily" - unset = True - dropbox_backup.set(df.fieldname, value) - - if unset: - dropbox_backup.set("send_backups_to_dropbox", 0) - - dropbox_backup.save() diff --git a/frappe/patches/v6_20x/__init__.py b/frappe/patches/v6_20x/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_20x/remove_roles_from_website_user.py b/frappe/patches/v6_20x/remove_roles_from_website_user.py deleted file mode 100644 index a4d579a1f0..0000000000 --- a/frappe/patches/v6_20x/remove_roles_from_website_user.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "user_email") - frappe.reload_doc("core", "doctype", "user") - for user_name in frappe.get_all('User', filters={'user_type': 'Website User'}): - user = frappe.get_doc('User', user_name) - if user.roles: - user.roles = [] - user.save() diff --git a/frappe/patches/v6_20x/set_allow_draft_for_print.py b/frappe/patches/v6_20x/set_allow_draft_for_print.py deleted file mode 100644 index 90c15e22b2..0000000000 --- a/frappe/patches/v6_20x/set_allow_draft_for_print.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.set_value("Print Settings", "Print Settings", "allow_print_for_draft", 1) \ No newline at end of file diff --git a/frappe/patches/v6_20x/update_insert_after.py b/frappe/patches/v6_20x/update_insert_after.py deleted file mode 100644 index 5ebec52fc9..0000000000 --- a/frappe/patches/v6_20x/update_insert_after.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals -import frappe, json - -def execute(): - for ps in frappe.get_all('Property Setter', filters={'property': '_idx'}, - fields = ['doc_type', 'value']): - custom_fields = frappe.get_all('Custom Field', - filters = {'dt': ps.doc_type}, fields=['name', 'fieldname']) - - if custom_fields: - _idx = json.loads(ps.value) - - for custom_field in custom_fields: - if custom_field.fieldname in _idx: - custom_field_idx = _idx.index(custom_field.fieldname) - if custom_field_idx == 0: - prev_fieldname = "" - - else: - prev_fieldname = _idx[custom_field_idx - 1] - - else: - prev_fieldname = _idx[-1] - custom_field_idx = len(_idx) - - frappe.db.set_value('Custom Field', custom_field.name, 'insert_after', prev_fieldname) - frappe.db.set_value('Custom Field', custom_field.name, 'idx', custom_field_idx) diff --git a/frappe/patches/v6_21/__init__.py b/frappe/patches/v6_21/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_21/print_settings_repeat_header_footer.py b/frappe/patches/v6_21/print_settings_repeat_header_footer.py deleted file mode 100644 index 941a145a54..0000000000 --- a/frappe/patches/v6_21/print_settings_repeat_header_footer.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Print Settings') - frappe.db.set_value('Print Settings', 'Print Settings', 'repeat_header_footer', 1) diff --git a/frappe/patches/v6_24/__init__.py b/frappe/patches/v6_24/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_24/set_language_as_code.py b/frappe/patches/v6_24/set_language_as_code.py deleted file mode 100644 index d685fd7d0e..0000000000 --- a/frappe/patches/v6_24/set_language_as_code.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.translate import get_lang_dict - -# migrate language from name to code -def execute(): - return diff --git a/frappe/patches/v6_4/__init__.py b/frappe/patches/v6_4/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_4/reduce_varchar_length.py b/frappe/patches/v6_4/reduce_varchar_length.py deleted file mode 100644 index 93a8be8c92..0000000000 --- a/frappe/patches/v6_4/reduce_varchar_length.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import unicode_literals, print_function -import frappe - -def execute(): - for doctype in frappe.get_all("DocType", filters={"issingle": 0}): - doctype = doctype.name - if not frappe.db.table_exists(doctype): - continue - - for column in frappe.db.sql("desc `tab{doctype}`".format(doctype=doctype), as_dict=True): - fieldname = column["Field"] - column_type = column["Type"] - - if not column_type.startswith("varchar"): - continue - - max_length = frappe.db.sql("""select max(char_length(`{fieldname}`)) from `tab{doctype}`"""\ - .format(fieldname=fieldname, doctype=doctype)) - - max_length = max_length[0][0] if max_length else None - - if max_length and 140 < max_length <= 255: - print( - "setting length of '{fieldname}' in '{doctype}' as {length}".format( - fieldname=fieldname, doctype=doctype, length=max_length) - ) - - # create property setter for length - frappe.make_property_setter({ - "doctype": doctype, - "fieldname": fieldname, - "property": "length", - "value": max_length, - "property_type": "Int" - }) - - frappe.clear_cache(doctype=doctype) diff --git a/frappe/patches/v6_4/rename_bengali_language.py b/frappe/patches/v6_4/rename_bengali_language.py deleted file mode 100644 index dbbcb62f8d..0000000000 --- a/frappe/patches/v6_4/rename_bengali_language.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe -from frappe.translate import rename_language - -def execute(): - rename_language("বাঙালি", "বাংলা") \ No newline at end of file diff --git a/frappe/patches/v6_6/__init__.py b/frappe/patches/v6_6/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_6/fix_file_url.py b/frappe/patches/v6_6/fix_file_url.py deleted file mode 100644 index 4f8956d343..0000000000 --- a/frappe/patches/v6_6/fix_file_url.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.meta import is_single - -def execute(): - """Fix old style file urls that start with files/""" - fix_file_urls() - fix_attach_field_urls() - -def fix_file_urls(): - for file in frappe.db.sql_list("""select name from `tabFile` where file_url like 'files/%'"""): - file = frappe.get_doc("File", file) - file.db_set("file_url", "/" + file.file_url, update_modified=False) - try: - file.validate_file() - file.db_set("file_name", file.file_name, update_modified=False) - if not file.content_hash: - file.generate_content_hash() - file.db_set("content_hash", file.content_hash, update_modified=False) - - except IOError: - pass - -def fix_attach_field_urls(): - # taken from an old patch - attach_fields = (frappe.db.sql("""select parent, fieldname from `tabDocField` where fieldtype in ('Attach', 'Attach Image')""") + - frappe.db.sql("""select dt, fieldname from `tabCustom Field` where fieldtype in ('Attach', 'Attach Image')""")) - - for doctype, fieldname in attach_fields: - if is_single(doctype): - frappe.db.sql("""update `tabSingles` set value=concat("/", `value`) - where doctype=%(doctype)s and field=%(fieldname)s - and value like 'files/%%'""", {"doctype": doctype, "fieldname": fieldname}) - else: - frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=concat("/", `{fieldname}`) - where `{fieldname}` like 'files/%'""".format(doctype=doctype, fieldname=fieldname)) diff --git a/frappe/patches/v6_6/rename_slovak_language.py b/frappe/patches/v6_6/rename_slovak_language.py deleted file mode 100644 index a942543372..0000000000 --- a/frappe/patches/v6_6/rename_slovak_language.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe -from frappe.translate import rename_language - -def execute(): - rename_language("slovenčina", "slovenčina (Slovak)") diff --git a/frappe/patches/v6_6/user_last_active.py b/frappe/patches/v6_6/user_last_active.py deleted file mode 100644 index fd55935174..0000000000 --- a/frappe/patches/v6_6/user_last_active.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype("User") - frappe.db.sql("update `tabUser` set last_active=last_login") diff --git a/frappe/patches/v6_9/__init__.py b/frappe/patches/v6_9/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v6_9/int_float_not_null.py b/frappe/patches/v6_9/int_float_not_null.py deleted file mode 100644 index 97495f9077..0000000000 --- a/frappe/patches/v6_9/int_float_not_null.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import cint, flt - -def execute(): - for doctype in frappe.get_all("DocType", filters={"issingle": 0}): - doctype = doctype.name - meta = frappe.get_meta(doctype) - - for column in frappe.db.sql("desc `tab{doctype}`".format(doctype=doctype), as_dict=True): - fieldname = column["Field"] - column_type = column["Type"] - - if not (column_type.startswith("int") or column_type.startswith("decimal")): - continue - - frappe.db.sql("""update `tab{doctype}` set `{fieldname}`=0 where `{fieldname}` is null"""\ - .format(doctype=doctype, fieldname=fieldname)) - - # alter table - if column["Null"]=='YES': - if not meta.get_field(fieldname): - continue - - default = cint(column["Default"]) if column_type.startswith("int") else flt(column["Default"]) - frappe.db.sql_ddl("""alter table `tab{doctype}` - change `{fieldname}` `{fieldname}` {column_type} not null default '{default}'""".format( - doctype=doctype, fieldname=fieldname, column_type=column_type, default=default)) - - diff --git a/frappe/patches/v6_9/rename_burmese_language.py b/frappe/patches/v6_9/rename_burmese_language.py deleted file mode 100644 index 66477f7efe..0000000000 --- a/frappe/patches/v6_9/rename_burmese_language.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -import frappe -from frappe.translate import rename_language - -def execute(): - rename_language("Melayu", "မြန်မာ") diff --git a/frappe/patches/v7_0/__init__.py b/frappe/patches/v7_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v7_0/add_communication_in_doc.py b/frappe/patches/v7_0/add_communication_in_doc.py deleted file mode 100644 index 4db02c5bab..0000000000 --- a/frappe/patches/v7_0/add_communication_in_doc.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.core.doctype.comment.comment import update_comment_in_doc - -def execute(): - for d in frappe.db.get_all("Communication", - fields = ['name', 'reference_doctype', 'reference_name', 'SUBSTRING(content,1,102)', 'communication_type'], - filters = {"reference_name":None,"reference_doctype":None,'communication_type': 'Communication'}): - - try: - update_comment_in_doc(d) - except frappe.ImplicitCommitError: - pass diff --git a/frappe/patches/v7_0/cleanup_list_settings.py b/frappe/patches/v7_0/cleanup_list_settings.py deleted file mode 100644 index e03ff57406..0000000000 --- a/frappe/patches/v7_0/cleanup_list_settings.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe, json - -def execute(): - if frappe.db.table_exists("__ListSettings"): - list_settings = frappe.db.sql("select user, doctype, data from __ListSettings", as_dict=1) - for ls in list_settings: - if ls and ls.data: - data = json.loads(ls.data) - if "fields" not in data: - continue - fields = data["fields"] - for field in fields: - if "name as" in field: - fields.remove(field) - data["fields"] = fields - - frappe.db.sql("update __ListSettings set data = %s where user=%s and doctype=%s", - (json.dumps(data), ls.user, ls.doctype)) - diff --git a/frappe/patches/v7_0/create_private_file_folder.py b/frappe/patches/v7_0/create_private_file_folder.py deleted file mode 100644 index bd26917a78..0000000000 --- a/frappe/patches/v7_0/create_private_file_folder.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe, os - -def execute(): - if not os.path.exists(os.path.join(frappe.local.site_path, 'private', 'files')): - frappe.create_folder(os.path.join(frappe.local.site_path, 'private', 'files')) \ No newline at end of file diff --git a/frappe/patches/v7_0/re_route.py b/frappe/patches/v7_0/re_route.py deleted file mode 100644 index cc36594ae8..0000000000 --- a/frappe/patches/v7_0/re_route.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.model.base_document import get_controller - -def execute(): - update_routes(['Blog Post', 'Blog Category', 'Web Page']) - -def update_routes(doctypes): - """Patch old routing system""" - for d in doctypes: - frappe.reload_doctype(d) - c = get_controller(d) - - condition = '' - if c.website.condition_field: - condition = 'where {0}=1'.format(c.website.condition_field) - - try: - frappe.db.sql("""update ignore `tab{0}` set route = concat(ifnull(parent_website_route, ""), - if(ifnull(parent_website_route, "")="", "", "/"), page_name) {1}""".format(d, condition)) - - except Exception as e: - if not frappe.db.is_missing_column(e): raise diff --git a/frappe/patches/v7_0/rename_bulk_email_to_email_queue.py b/frappe/patches/v7_0/rename_bulk_email_to_email_queue.py deleted file mode 100644 index 9a7a756144..0000000000 --- a/frappe/patches/v7_0/rename_bulk_email_to_email_queue.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.rename_doc('DocType', 'Bulk Email', 'Email Queue') \ No newline at end of file diff --git a/frappe/patches/v7_0/rename_newsletter_list_to_email_group.py b/frappe/patches/v7_0/rename_newsletter_list_to_email_group.py deleted file mode 100644 index 79061d383c..0000000000 --- a/frappe/patches/v7_0/rename_newsletter_list_to_email_group.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.rename_doc('DocType', 'Newsletter List', 'Email Group') - frappe.rename_doc('DocType', 'Newsletter List Subscriber', 'Email Group Member') \ No newline at end of file diff --git a/frappe/patches/v7_0/set_email_group.py b/frappe/patches/v7_0/set_email_group.py deleted file mode 100644 index e3dd66ebf3..0000000000 --- a/frappe/patches/v7_0/set_email_group.py +++ /dev/null @@ -1,11 +0,0 @@ -# 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 - -def execute(): - frappe.reload_doc("email", "doctype", "email_group_member") - if "newsletter_list" in frappe.db.get_table_columns("Email Group Member"): - frappe.db.sql("""update `tabEmail Group Member` set email_group = newsletter_list - where email_group is null or email_group = ''""") \ No newline at end of file diff --git a/frappe/patches/v7_0/set_user_fullname.py b/frappe/patches/v7_0/set_user_fullname.py deleted file mode 100644 index a7c6670f45..0000000000 --- a/frappe/patches/v7_0/set_user_fullname.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("Core", "DocType", "User") - - for user in frappe.db.get_all('User'): - user = frappe.get_doc('User', user.name) - user.set_full_name() - user.db_set('full_name', user.full_name, update_modified = False) \ No newline at end of file diff --git a/frappe/patches/v7_0/update_auth.py b/frappe/patches/v7_0/update_auth.py deleted file mode 100644 index 3d47edf4b5..0000000000 --- a/frappe/patches/v7_0/update_auth.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils.password import create_auth_table, set_encrypted_password - -def execute(): - if '__OldAuth' not in frappe.db.get_tables(): - frappe.db.sql_ddl('''alter table `__Auth` rename `__OldAuth`''') - - create_auth_table() - - # user passwords - frappe.db.sql('''insert ignore into `__Auth` (doctype, name, fieldname, `password`) - (select 'User', `name`, 'password', `password` from `__OldAuth`)''') - - frappe.db.commit() - - # other password fields - for doctype in frappe.db.sql_list('''select distinct parent from `tabDocField` - where fieldtype="Password" and parent != "User"'''): - - frappe.reload_doctype(doctype) - meta = frappe.get_meta(doctype) - - for df in meta.get('fields', {'fieldtype': 'Password'}): - if meta.issingle: - password = frappe.db.get_value(doctype, doctype, df.fieldname) - if password: - set_encrypted_password(doctype, doctype, password, fieldname=df.fieldname) - frappe.db.set_value(doctype, doctype, df.fieldname, '*'*len(password)) - - else: - for d in frappe.db.sql('''select name, `{fieldname}` from `tab{doctype}` - where `{fieldname}` is not null'''.format(fieldname=df.fieldname, doctype=doctype), as_dict=True): - - set_encrypted_password(doctype, d.name, d.get(df.fieldname), fieldname=df.fieldname) - - frappe.db.sql('''update `tab{doctype}` set `{fieldname}`=repeat("*", char_length(`{fieldname}`))''' - .format(doctype=doctype, fieldname=df.fieldname)) - - frappe.db.commit() - - frappe.db.sql_ddl('''drop table `__OldAuth`''') diff --git a/frappe/patches/v7_0/update_report_builder_json.py b/frappe/patches/v7_0/update_report_builder_json.py deleted file mode 100644 index a344ca5412..0000000000 --- a/frappe/patches/v7_0/update_report_builder_json.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - for report in frappe.db.sql_list(""" select name from `tabReport` where report_type = 'Report Builder' - and is_standard = 'No' and `json` != '' and `json` is not null """): - doc = frappe.get_doc("Report", report) - doc.update_report_json() - doc.db_set("json", doc.json, update_modified=False) \ No newline at end of file diff --git a/frappe/patches/v7_0/update_send_after_in_bulk_email.py b/frappe/patches/v7_0/update_send_after_in_bulk_email.py deleted file mode 100644 index 1b08309b6a..0000000000 --- a/frappe/patches/v7_0/update_send_after_in_bulk_email.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import now_datetime - -def execute(): - frappe.db.sql('update `tabEmail Queue` set send_after=%s where send_after is null', now_datetime()) \ No newline at end of file diff --git a/frappe/patches/v7_1/__init__.py b/frappe/patches/v7_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v7_1/disabled_print_settings_for_custom_print_format.py b/frappe/patches/v7_1/disabled_print_settings_for_custom_print_format.py deleted file mode 100644 index c74d2d98f9..0000000000 --- a/frappe/patches/v7_1/disabled_print_settings_for_custom_print_format.py +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 -*- -# 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 - -def execute(): - frappe.reload_doctype('Print Format') - frappe.db.sql(""" - update - `tabPrint Format` - set - align_labels_right = 0, line_breaks = 0, show_section_headings = 0 - where - custom_format = 1 - """) diff --git a/frappe/patches/v7_1/refactor_integration_broker.py b/frappe/patches/v7_1/refactor_integration_broker.py deleted file mode 100644 index 8c9aaa6795..0000000000 --- a/frappe/patches/v7_1/refactor_integration_broker.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# 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 -import json - -def execute(): - for doctype_name in ["Razorpay Log", "Razorpay Payment", "Razorpay Settings"]: - delete_doc("DocType", doctype_name) - - reload_doctypes() - setup_services() - -def delete_doc(doctype, doctype_name): - frappe.delete_doc(doctype, doctype_name) - -def reload_doctypes(): - for doctype in ("razorpay_settings", "paypal_settings", "dropbox_settings", "ldap_settings"): - frappe.reload_doc("integrations", "doctype", doctype) - -def setup_services(): - for service in [{"old_name": "Razorpay", "new_name": "Razorpay"}, - {"old_name": "PayPal", "new_name": "PayPal"}, - {"old_name": "Dropbox Integration", "new_name": "Dropbox"}, - {"old_name": "LDAP Auth", "new_name": "LDAP"}]: - - try: - service_doc = frappe.get_doc("Integration Service", service["old_name"]) - settings = json.loads(service_doc.custom_settings_json) - - service_settings = frappe.new_doc("{0} Settings".format(service["new_name"])) - service_settings.update(settings) - - service_settings.flags.ignore_mandatory = True - service_settings.save(ignore_permissions=True) - - if service["old_name"] in ["Dropbox Integration", "LDAP Auth"]: - delete_doc("Integration Service", service["old_name"]) - - new_service_doc = frappe.get_doc({ - "doctype": "Integration Service", - "service": service["new_name"], - "enabled": 1 - }) - - new_service_doc.flags.ignore_mandatory = True - new_service_doc.save(ignore_permissions=True) - - except Exception: - pass diff --git a/frappe/patches/v7_1/rename_chinese_language_codes.py b/frappe/patches/v7_1/rename_chinese_language_codes.py deleted file mode 100644 index 1ed25a4959..0000000000 --- a/frappe/patches/v7_1/rename_chinese_language_codes.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.rename_doc('Language', 'zh-cn', 'zh', force=True, - merge=True if frappe.db.exists('Language', 'zh') else False) - if frappe.db.get_value('Language', 'zh-tw') == 'zh-tw': - frappe.rename_doc('Language', 'zh-tw', 'zh-TW', force=True) - - frappe.db.set_value('Language', 'zh', 'language_code', 'zh') - frappe.db.set_value('Language', 'zh-TW', 'language_code', 'zh-TW') \ No newline at end of file diff --git a/frappe/patches/v7_1/rename_scheduler_log_to_error_log.py b/frappe/patches/v7_1/rename_scheduler_log_to_error_log.py deleted file mode 100644 index 4d1a39538f..0000000000 --- a/frappe/patches/v7_1/rename_scheduler_log_to_error_log.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if not 'tabError Log' in frappe.db.get_tables(): - frappe.rename_doc('DocType', 'Scheduler Log', 'Error Log') - frappe.db.sql("""delete from `tabError Log` where datediff(curdate(), creation) > 30""") - frappe.db.commit() - frappe.db.sql('alter table `tabError Log` change column name name varchar(140)') - frappe.db.sql('alter table `tabError Log` change column parent parent varchar(140)') - frappe.db.sql('alter table `tabError Log` engine=MyISAM') diff --git a/frappe/patches/v7_1/set_backup_limit.py b/frappe/patches/v7_1/set_backup_limit.py deleted file mode 100644 index 7b0a344305..0000000000 --- a/frappe/patches/v7_1/set_backup_limit.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -from frappe.utils import cint -import frappe - -def execute(): - frappe.reload_doctype('System Settings') - backup_limit = frappe.db.get_single_value('System Settings', 'backup_limit') - - if cint(backup_limit) == 0: - frappe.db.set_value('System Settings', 'System Settings', 'backup_limit', 3) diff --git a/frappe/patches/v7_1/setup_integration_services.py b/frappe/patches/v7_1/setup_integration_services.py deleted file mode 100644 index 1c70b8e835..0000000000 --- a/frappe/patches/v7_1/setup_integration_services.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.exceptions import DataError -from frappe.utils.password import get_decrypted_password -from frappe.utils import cstr -import os - -app_list = [ - {"app_name": "razorpay_integration", "service_name": "Razorpay", "doctype": "Razorpay Settings", "remove": True}, - {"app_name": "paypal_integration", "service_name": "PayPal", "doctype": "PayPal Settings", "remove": True}, - {"app_name": "frappe", "service_name": "Dropbox", "doctype": "Dropbox Backup", "remove": False} -] - -def execute(): - installed_apps = frappe.get_installed_apps() - - for app_details in app_list: - if app_details["app_name"] in installed_apps: - settings = get_app_settings(app_details) - if app_details["remove"]: - uninstall_app(app_details["app_name"]) - - try: - setup_integration_service(app_details, settings) - except DataError: - pass - - frappe.delete_doc("DocType", "Dropbox Backup") - -def setup_integration_service(app_details, settings=None): - if not settings: - return - - setup_service_settings(app_details["service_name"], settings) - - doc_path = frappe.get_app_path("frappe", "integration_broker", "doctype", - "integration_service", "integration_service.json") - - if not os.path.exists(doc_path): - return - - frappe.reload_doc("integration_broker", "doctype", "integration_service") - - if frappe.db.exists("Integration Service", app_details["service_name"]): - integration_service = frappe.get_doc("Integration Service", app_details["service_name"]) - else: - integration_service = frappe.new_doc("Integration Service") - integration_service.service = app_details["service_name"] - - integration_service.enabled = 1 - integration_service.flags.ignore_mandatory = True - integration_service.save(ignore_permissions=True) - -def get_app_settings(app_details): - parameters = {} - doctype = docname = app_details["doctype"] - - app_settings = get_parameters(app_details) - if app_settings: - settings = app_settings["settings"] - frappe.reload_doc("integrations", "doctype", "{0}_settings".format(app_details["service_name"].lower())) - controller = frappe.get_meta("{0} Settings".format(app_details["service_name"])) - - for d in controller.fields: - if settings.get(d.fieldname): - if ''.join(set(cstr(settings.get(d.fieldname)))) == '*': - setattr(settings, d.fieldname, get_decrypted_password(doctype, docname, d.fieldname, raise_exception=True)) - - parameters.update({d.fieldname : settings.get(d.fieldname)}) - - return parameters - -def uninstall_app(app_name): - from frappe.installer import remove_from_installed_apps - remove_from_installed_apps(app_name) - -def get_parameters(app_details): - if app_details["service_name"] == "Razorpay": - return {"settings": frappe.get_doc(app_details["doctype"])} - - elif app_details["service_name"] == "PayPal": - if frappe.conf.paypal_username and frappe.conf.paypal_password and frappe.conf.paypal_signature: - return { - "settings": { - "api_username": frappe.conf.paypal_username, - "api_password": frappe.conf.paypal_password, - "signature": frappe.conf.paypal_signature - } - } - else: - return {"settings": frappe.get_doc(app_details["doctype"])} - - elif app_details["service_name"] == "Dropbox": - doc = frappe.db.get_value(app_details["doctype"], None, - ["dropbox_access_key", "dropbox_access_secret", "upload_backups_to_dropbox"], as_dict=1) - - if not doc: - return - - if not (frappe.conf.dropbox_access_key and frappe.conf.dropbox_secret_key): - return - - return { - "settings": { - "app_access_key": frappe.conf.dropbox_access_key, - "app_secret_key": frappe.conf.dropbox_secret_key, - "dropbox_access_key": doc.dropbox_access_key, - "dropbox_access_secret": doc.dropbox_access_secret, - "backup_frequency": doc.upload_backups_to_dropbox, - "enabled": doc.send_backups_to_dropbox - } - } - -def setup_service_settings(service_name, settings): - service_doc = frappe.get_doc("{0} Settings".format(service_name)) - service_doc.update(settings) - service_doc.flags.ignore_mandatory = True - service_doc.save(ignore_permissions=True) \ No newline at end of file diff --git a/frappe/patches/v7_1/sync_language_doctype.py b/frappe/patches/v7_1/sync_language_doctype.py deleted file mode 100644 index 83d1a4f5a6..0000000000 --- a/frappe/patches/v7_1/sync_language_doctype.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.translate import get_lang_dict - -def execute(): - frappe.reload_doc('core', 'doctype', 'language') - - from frappe.core.doctype.language.language import sync_languages - sync_languages() - - # move language from old style to new style for old accounts - # i.e. from "english" to "en" - - lang_dict = get_lang_dict() - language = frappe.db.get_value('System Settings', None, 'language') - if language: - frappe.db.set_value('System Settings', None, 'language', lang_dict.get('language') or 'en') - - for user in frappe.get_all('User', fields=['name', 'language']): - if user.language: - frappe.db.set_value('User', user.name, 'language', - lang_dict.get('language') or 'en', update_modified=False) diff --git a/frappe/patches/v7_2/__init__.py b/frappe/patches/v7_2/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v7_2/fix_email_queue_recipient.py b/frappe/patches/v7_2/fix_email_queue_recipient.py deleted file mode 100644 index 645b17b5c9..0000000000 --- a/frappe/patches/v7_2/fix_email_queue_recipient.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('email', 'doctype', 'email_queue_recipient') - frappe.db.sql('update `tabEmail Queue Recipient` set parenttype="recipients"') \ No newline at end of file diff --git a/frappe/patches/v7_2/merge_knowledge_base.py b/frappe/patches/v7_2/merge_knowledge_base.py deleted file mode 100644 index 301d15e1dd..0000000000 --- a/frappe/patches/v7_2/merge_knowledge_base.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe.patches.v7_0.re_route import update_routes -from frappe.installer import remove_from_installed_apps - -def execute(): - if 'knowledge_base' in frappe.get_installed_apps(): - frappe.reload_doc('website', 'doctype', 'help_category') - frappe.reload_doc('website', 'doctype', 'help_article') - update_routes(['Help Category', 'Help Article']) - remove_from_installed_apps('knowledge_base') - - # remove module def - if frappe.db.exists('Module Def', 'Knowledge Base'): - frappe.delete_doc('Module Def', 'Knowledge Base') - - # set missing routes - for doctype in ('Help Category', 'Help Article'): - for d in frappe.get_all(doctype, fields=['name', 'route']): - if not d.route: - doc = frappe.get_doc(doctype, d.name) - doc.set_route() - doc.db_update() \ No newline at end of file diff --git a/frappe/patches/v7_2/remove_in_filter.py b/frappe/patches/v7_2/remove_in_filter.py deleted file mode 100644 index 36556d7c13..0000000000 --- a/frappe/patches/v7_2/remove_in_filter.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.has_column('DocField', 'in_filter'): - frappe.db.sql('alter table tabDocField drop column in_filter') - frappe.clear_cache(doctype="DocField") \ No newline at end of file diff --git a/frappe/patches/v7_2/set_doctype_engine.py b/frappe/patches/v7_2/set_doctype_engine.py deleted file mode 100644 index 3a5cc384a2..0000000000 --- a/frappe/patches/v7_2/set_doctype_engine.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - for t in frappe.db.sql('show table status'): - if t[0].startswith('tab'): - frappe.db.sql('update tabDocType set engine=%s where name=%s', (t[1], t[0][3:])) \ No newline at end of file diff --git a/frappe/patches/v7_2/set_in_standard_filter_property.py b/frappe/patches/v7_2/set_in_standard_filter_property.py deleted file mode 100644 index 12f97f7f8e..0000000000 --- a/frappe/patches/v7_2/set_in_standard_filter_property.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('custom', 'doctype', 'custom_field', force=True) - - try: - frappe.db.sql('update `tabCustom Field` set in_standard_filter = in_filter_dash') - except Exception as e: - if not frappe.db.is_missing_column(e): raise e - - for doctype in frappe.get_all("DocType", {"istable": 0, "issingle": 0, "custom": 0}): - try: - frappe.reload_doctype(doctype.name, force=True) - except KeyError: - pass - except frappe.db.DataError: - pass - except Exception: - pass diff --git a/frappe/patches/v7_2/setup_custom_perms.py b/frappe/patches/v7_2/setup_custom_perms.py deleted file mode 100644 index 1b3b86236c..0000000000 --- a/frappe/patches/v7_2/setup_custom_perms.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.permissions import setup_custom_perms -from frappe.core.page.permission_manager.permission_manager import get_standard_permissions -from frappe.utils.reset_doc import setup_perms_for - -''' -Copy DocPerm to Custom DocPerm where permissions are set differently -''' - -def execute(): - for d in frappe.db.get_all('DocType', dict(istable=0, issingle=0, custom=0)): - setup_perms_for(d.name) diff --git a/frappe/patches/v7_2/setup_ldap_config.py b/frappe/patches/v7_2/setup_ldap_config.py deleted file mode 100644 index 31dd8ca6fe..0000000000 --- a/frappe/patches/v7_2/setup_ldap_config.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils import cint - -def execute(): - frappe.reload_doc("integrations", "doctype", "ldap_settings") - - if not frappe.db.exists("DocType", "Integration Service"): - return - - if not frappe.db.exists("Integration Service", "LDAP"): - return - - if not cint(frappe.db.get_value("Integration Service", "LDAP", 'enabled')): - return - - import ldap - try: - ldap_settings = frappe.get_doc("LDAP Settings") - ldap_settings.save(ignore_permissions=True) - except ldap.LDAPError: - pass diff --git a/frappe/patches/v7_2/update_communications.py b/frappe/patches/v7_2/update_communications.py deleted file mode 100644 index f3d859b95a..0000000000 --- a/frappe/patches/v7_2/update_communications.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - """ - in communication move feedback details to content - remove Guest None from sender full name - setup feedback request trigger's is_manual field - """ - return diff --git a/frappe/patches/v7_2/update_feedback_request.py b/frappe/patches/v7_2/update_feedback_request.py deleted file mode 100644 index 11e9eb8e92..0000000000 --- a/frappe/patches/v7_2/update_feedback_request.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - """ - rename feedback request documents, - update the feedback request and save the rating and communication - reference in Feedback Request document - """ - return diff --git a/frappe/patches/v8_0/__init__.py b/frappe/patches/v8_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v8_0/deprecate_integration_broker.py b/frappe/patches/v8_0/deprecate_integration_broker.py deleted file mode 100644 index ad1a7d9571..0000000000 --- a/frappe/patches/v8_0/deprecate_integration_broker.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.integrations.utils import create_payment_gateway - -def execute(): - setup_enabled_integrations() - - for doctype in ["integration_request", "oauth_authorization_code", "oauth_bearer_token", "oauth_client"]: - frappe.reload_doc('integrations', 'doctype', doctype) - - frappe.reload_doc("core", "doctype", "payment_gateway") - update_doctype_module() - create_payment_gateway_master_records() - - for doctype in ["Integration Service", "Integration Service Parameter"]: - frappe.delete_doc("DocType", doctype) - - if not frappe.db.get_value("DocType", {"module": "Integration Broker"}, "name"): - frappe.delete_doc("Module Def", "Integration Broker") - -def setup_enabled_integrations(): - if not frappe.db.exists("DocType", "Integration Service"): - return - - for service in frappe.get_all("Integration Service", - filters={"enabled": 1, "service": ('in', ("Dropbox", "LDAP"))}, fields=["name"]): - - doctype = "{0} Settings".format(service.name) - frappe.db.set_value(doctype, doctype, 'enabled', 1) - -def update_doctype_module(): - frappe.db.sql("""update tabDocType set module='Integrations' - where name in ('Integration Request', 'Oauth Authorization Code', - 'Oauth Bearer Token', 'Oauth Client') """) - - frappe.db.sql(""" update tabDocType set module='Core' where name = 'Payment Gateway'""") - -def create_payment_gateway_master_records(): - for payment_gateway in ["Razorpay", "PayPal"]: - doctype = "{0} Settings".format(payment_gateway) - doc = frappe.get_doc(doctype) - doc_meta = frappe.get_meta(doctype) - all_mandatory_fields_has_value = True - - for d in doc_meta.fields: - if d.reqd and not doc.get(d.fieldname): - all_mandatory_fields_has_value = False - break - - if all_mandatory_fields_has_value: - create_payment_gateway(payment_gateway) diff --git a/frappe/patches/v8_0/drop_in_dialog.py b/frappe/patches/v8_0/drop_in_dialog.py deleted file mode 100644 index 231d757f26..0000000000 --- a/frappe/patches/v8_0/drop_in_dialog.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.has_column('DocType', 'in_dialog'): - frappe.db.sql('alter table tabDocType drop column in_dialog') - frappe.clear_cache(doctype="DocType") \ No newline at end of file diff --git a/frappe/patches/v8_0/drop_is_custom_from_docperm.py b/frappe/patches/v8_0/drop_is_custom_from_docperm.py deleted file mode 100644 index 4530dcd2e0..0000000000 --- a/frappe/patches/v8_0/drop_is_custom_from_docperm.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('DocPerm') - if frappe.db.has_column('DocPerm', 'is_custom'): - frappe.db.commit() - frappe.db.sql('alter table `tabDocPerm` drop column is_custom') \ No newline at end of file diff --git a/frappe/patches/v8_0/drop_unwanted_indexes.py b/frappe/patches/v8_0/drop_unwanted_indexes.py deleted file mode 100644 index fc66ed43fd..0000000000 --- a/frappe/patches/v8_0/drop_unwanted_indexes.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals -import frappe - -def execute(): - # communication - unwanted_indexes = ["communication_date_index", "message_id_index", "modified_index", - "creation_index", "reference_owner", "communication_date"] - - for k in unwanted_indexes: - try: - frappe.db.sql("drop index {0} on `tabCommunication`".format(k)) - except: - pass \ No newline at end of file diff --git a/frappe/patches/v8_0/install_new_build_system_requirements.py b/frappe/patches/v8_0/install_new_build_system_requirements.py deleted file mode 100644 index 536c2fcfb3..0000000000 --- a/frappe/patches/v8_0/install_new_build_system_requirements.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import print_function, unicode_literals -from subprocess import Popen, call, PIPE - -def execute(): - # update nodejs version if brew exists - p = Popen(['which', 'brew'], stdout=PIPE, stderr=PIPE) - output, err = p.communicate() - if output: - call(['brew', 'upgrade', 'node']) - else: - print('Please update your NodeJS version') - - call([ - 'npm', 'install', - 'babel-core', - 'less', - 'chokidar', - 'babel-preset-es2015', - 'babel-preset-es2016', - 'babel-preset-es2017', - 'babel-preset-babili' - ]) \ No newline at end of file diff --git a/frappe/patches/v8_0/newsletter_childtable_migrate.py b/frappe/patches/v8_0/newsletter_childtable_migrate.py deleted file mode 100644 index f652b37f56..0000000000 --- a/frappe/patches/v8_0/newsletter_childtable_migrate.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('email', 'doctype', 'newsletter_email_group') - frappe.reload_doctype('Newsletter') - - if "email_group" not in frappe.db.get_table_columns("Newsletter"): - return - - newsletters = frappe.get_all("Newsletter", fields=["name", "email_group"]) - for newsletter in newsletters: - if newsletter.email_group: - newsletter_doc = frappe.get_doc("Newsletter", newsletter.name) - if not newsletter_doc.get("email_group"): - newsletter_doc.append("email_group", { - "email_group": newsletter.email_group, - }) - newsletter_doc.flags.ignore_validate = True - newsletter_doc.flags.ignore_mandatory = True - newsletter_doc.save() diff --git a/frappe/patches/v8_0/rename_listsettings_to_usersettings.py b/frappe/patches/v8_0/rename_listsettings_to_usersettings.py deleted file mode 100644 index 584e4a1111..0000000000 --- a/frappe/patches/v8_0/rename_listsettings_to_usersettings.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import unicode_literals -from frappe.model.utils.user_settings import update_user_settings -import frappe, json -from six import iteritems - - -def execute(): - if frappe.db.table_exists("__ListSettings"): - for us in frappe.db.sql('''select user, doctype, data from __ListSettings''', as_dict=True): - try: - data = json.loads(us.data) - except: - continue - - if 'List' in data: - continue - - if 'limit' in data: - data['page_length'] = data['limit'] - del data['limit'] - - new_data = dict(List=data) - new_data = json.dumps(new_data) - - frappe.db.sql('''update __ListSettings - set data=%(new_data)s - where user=%(user)s - and doctype=%(doctype)s''', - {'new_data': new_data, 'user': us.user, 'doctype': us.doctype}) - - frappe.db.sql("RENAME TABLE __ListSettings to __UserSettings") - else: - if not frappe.db.table_exists("__UserSettings"): - frappe.db.create_user_settings_table() - - for user in frappe.db.get_all('User', {'user_type': 'System User'}): - defaults = frappe.defaults.get_defaults_for(user.name) - for key, value in iteritems(defaults): - if key.startswith('_list_settings:'): - doctype = key.replace('_list_settings:', '') - columns = ['`tab{1}`.`{0}`'.format(*c) for c in json.loads(value)] - for col in columns: - if "name as" in col: - columns.remove(col) - - update_user_settings(doctype, {'fields': columns}) \ No newline at end of file diff --git a/frappe/patches/v8_0/rename_page_role_to_has_role.py b/frappe/patches/v8_0/rename_page_role_to_has_role.py deleted file mode 100644 index 9c610d857d..0000000000 --- a/frappe/patches/v8_0/rename_page_role_to_has_role.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - if not frappe.db.exists('DocType', 'Has Role'): - frappe.rename_doc('DocType', 'Page Role', 'Has Role') - reload_doc() - set_ref_doctype_roles_to_report() - copy_user_roles_to_has_roles() - remove_doctypes() - -def reload_doc(): - frappe.reload_doc("core", 'doctype', "page") - frappe.reload_doc("core", 'doctype', "report") - frappe.reload_doc("core", 'doctype', "user") - frappe.reload_doc("core", 'doctype', "has_role") - -def set_ref_doctype_roles_to_report(): - for data in frappe.get_all('Report', fields=["name"]): - doc = frappe.get_doc('Report', data.name) - if frappe.db.exists("DocType", doc.ref_doctype): - try: - doc.set_doctype_roles() - for row in doc.roles: - row.db_update() - except: - pass - -def copy_user_roles_to_has_roles(): - if frappe.db.exists('DocType', 'UserRole'): - for data in frappe.get_all('User', fields = ["name"]): - doc = frappe.get_doc('User', data.name) - doc.set('roles',[]) - for args in frappe.get_all('UserRole', fields = ["role"], - filters = {'parent': data.name, 'parenttype': 'User'}): - doc.append('roles', { - 'role': args.role - }) - for role in doc.roles: - role.db_update() - -def remove_doctypes(): - for doctype in ['UserRole', 'Event Role']: - if frappe.db.exists('DocType', doctype): - frappe.delete_doc('DocType', doctype) \ No newline at end of file diff --git a/frappe/patches/v8_0/rename_print_to_printing.py b/frappe/patches/v8_0/rename_print_to_printing.py deleted file mode 100644 index ecdbc3f7be..0000000000 --- a/frappe/patches/v8_0/rename_print_to_printing.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if frappe.db.exists('Module Def', 'Print'): - frappe.reload_doc('printing', 'doctype', 'print_format') - frappe.reload_doc('printing', 'doctype', 'print_settings') - frappe.reload_doc('printing', 'doctype', 'print_heading') - frappe.reload_doc('printing', 'doctype', 'letter_head') - frappe.reload_doc('printing', 'page', 'print_format_builder') - frappe.db.sql("""update `tabPrint Format` set module='Printing' where module='Print'""") - - frappe.delete_doc('Module Def', 'Print') \ No newline at end of file diff --git a/frappe/patches/v8_0/set_allow_traceback.py b/frappe/patches/v8_0/set_allow_traceback.py deleted file mode 100644 index 3eceb3e29c..0000000000 --- a/frappe/patches/v8_0/set_allow_traceback.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('core', 'doctype', 'system_settings') - frappe.db.sql("update `tabSystem Settings` set allow_error_traceback=1") diff --git a/frappe/patches/v8_0/set_currency_field_precision.py b/frappe/patches/v8_0/set_currency_field_precision.py deleted file mode 100644 index 89835c8c1e..0000000000 --- a/frappe/patches/v8_0/set_currency_field_precision.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import get_number_format_info - -def execute(): - frappe.reload_doc('core', 'doctype', 'system_settings', force=True) - if not frappe.db.get_value("System Settings", None, "currency_precision"): - default_currency = frappe.db.get_default("currency") - number_format = frappe.db.get_value("Currency", default_currency, "number_format", cache=True) \ - or frappe.db.get_default("number_format") - if number_format: - precision = get_number_format_info(number_format)[2] - else: - precision = 2 - - ss = frappe.get_doc("System Settings") - ss.currency_precision = precision - ss.flags.ignore_mandatory = True - ss.save() diff --git a/frappe/patches/v8_0/set_doctype_values_in_custom_role.py b/frappe/patches/v8_0/set_doctype_values_in_custom_role.py deleted file mode 100644 index 58cdc4497d..0000000000 --- a/frappe/patches/v8_0/set_doctype_values_in_custom_role.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doctype('Custom Role') - - # set ref doctype in custom role for reports - frappe.db.sql(""" update `tabCustom Role` set - `tabCustom Role`.ref_doctype = (select ref_doctype from `tabReport` where name = `tabCustom Role`.report) - where `tabCustom Role`.report is not null""") diff --git a/frappe/patches/v8_0/set_user_permission_for_page_and_report.py b/frappe/patches/v8_0/set_user_permission_for_page_and_report.py deleted file mode 100644 index 560ea46db2..0000000000 --- a/frappe/patches/v8_0/set_user_permission_for_page_and_report.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - if not frappe.db.exists('DocType', 'Custom Role'): - frappe.reload_doc("core", 'doctype', "custom_role") - set_user_permission_for_page_and_report() - - update_ref_doctype_in_custom_role() - -def update_ref_doctype_in_custom_role(): - frappe.reload_doc("core", 'doctype', "custom_role") - frappe.db.sql("""update `tabCustom Role` - set - ref_doctype = (select ref_doctype from tabReport where name = `tabCustom Role`.report) - where report is not null""") - -def set_user_permission_for_page_and_report(): - make_custom_roles_for_page_and_report() - -def make_custom_roles_for_page_and_report(): - for doctype in ['Page', 'Report']: - for data in get_data(doctype): - doc = frappe.get_doc(doctype, data.name) - roles = get_roles(doctype, data, doc) - make_custom_roles(doctype, doc.name, roles) - -def get_data(doctype): - fields = ["name"] if doctype == 'Page' else ["name", "ref_doctype"] - return frappe.get_all(doctype, fields = fields) - -def get_roles(doctype, data, doc): - roles = [] - if doctype == 'Page': - for d in doc.roles: - if frappe.db.exists('Role', d.role): - roles.append({'role': d.role}) - else: - out = frappe.get_all('Custom DocPerm', fields='distinct role', filters=dict(parent = data.ref_doctype)) - for d in out: - roles.append({'role': d.role}) - return roles - -def make_custom_roles(doctype, name, roles): - field = doctype.lower() - - if roles: - custom_permission = frappe.get_doc({ - 'doctype': 'Custom Role', - field : name, - 'roles' : roles - }).insert() diff --git a/frappe/patches/v8_0/setup_email_inbox.py b/frappe/patches/v8_0/setup_email_inbox.py deleted file mode 100644 index 1bfe3b0b74..0000000000 --- a/frappe/patches/v8_0/setup_email_inbox.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import unicode_literals -import frappe, json -from frappe.core.doctype.user.user import ask_pass_update, setup_user_email_inbox - -def execute(): - """ - depricate email inbox page if exists - remove desktop icon for email inbox page if exists - patch to remove Custom DocPerm for communication - add user inbox child table entry for existing email account in not exists - """ - - if frappe.db.exists("Page", "email_inbox"): - frappe.delete_doc("Page", "email_inbox") - - frappe.db.sql("""update `tabCustom DocPerm` set `write`=0, email=1 where parent='Communication'""") - - frappe.reload_doc("core", "doctype", "user_email") - frappe.reload_doc("email", "doctype", "email_account") - - email_accounts = frappe.get_all("Email Account", filters={"enable_incoming": 1}, - fields=["name", "email_id", "awaiting_password", "enable_outgoing"]) - - for email_account in email_accounts: - setup_user_email_inbox(email_account.get("name"), email_account.get("awaiting_password"), - email_account.get("email_id"), email_account.get("enabled_outgoing")) diff --git a/frappe/patches/v8_0/update_gender_and_salutation.py b/frappe/patches/v8_0/update_gender_and_salutation.py deleted file mode 100644 index bcd9d4cbd7..0000000000 --- a/frappe/patches/v8_0/update_gender_and_salutation.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors - -from __future__ import unicode_literals -import frappe -from frappe.desk.page.setup_wizard.install_fixtures import update_genders, update_salutations - -def execute(): - frappe.db.set_value("DocType", "Contact", "module", "Contacts") - frappe.db.set_value("DocType", "Address", "module", "Contacts") - frappe.db.set_value("DocType", "Address Template", "module", "Contacts") - frappe.reload_doc('contacts', 'doctype', 'gender') - frappe.reload_doc('contacts', 'doctype', 'salutation') - - update_genders() - update_salutations() \ No newline at end of file diff --git a/frappe/patches/v8_0/update_global_search_table.py b/frappe/patches/v8_0/update_global_search_table.py deleted file mode 100644 index 3c0a70155b..0000000000 --- a/frappe/patches/v8_0/update_global_search_table.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - if not 'published' in frappe.db.get_db_table_columns('__global_search'): - frappe.db.sql('''alter table __global_search - add column `title` varchar(140)''') - - frappe.db.sql('''alter table __global_search - add column `route` varchar(140)''') - - frappe.db.sql('''alter table __global_search - add column `published` int(1) not null default 0''') diff --git a/frappe/patches/v8_0/update_published_in_global_search.py b/frappe/patches/v8_0/update_published_in_global_search.py deleted file mode 100644 index a378f24732..0000000000 --- a/frappe/patches/v8_0/update_published_in_global_search.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - from frappe.website.router import get_doctypes_with_web_view - from frappe.utils.global_search import rebuild_for_doctype - - for doctype in get_doctypes_with_web_view(): - try: - rebuild_for_doctype(doctype) - except frappe.DoesNotExistError: - pass diff --git a/frappe/patches/v8_0/update_records_in_global_search.py b/frappe/patches/v8_0/update_records_in_global_search.py deleted file mode 100644 index dafa1e76d3..0000000000 --- a/frappe/patches/v8_0/update_records_in_global_search.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils.global_search import get_doctypes_with_global_search, rebuild_for_doctype -from frappe.utils import update_progress_bar - -def execute(): - frappe.cache().delete_value('doctypes_with_global_search') - doctypes_with_global_search = get_doctypes_with_global_search(with_child_tables=False) - - for i, doctype in enumerate(doctypes_with_global_search): - update_progress_bar("Updating Global Search", i, len(doctypes_with_global_search)) - rebuild_for_doctype(doctype) diff --git a/frappe/patches/v8_1/__init__.py b/frappe/patches/v8_1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v8_1/delete_custom_docperm_if_doctype_not_exists.py b/frappe/patches/v8_1/delete_custom_docperm_if_doctype_not_exists.py deleted file mode 100644 index 92b54edfd4..0000000000 --- a/frappe/patches/v8_1/delete_custom_docperm_if_doctype_not_exists.py +++ /dev/null @@ -1,7 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""delete from `tabCustom DocPerm` - where parent not in ( select name from `tabDocType` ) - """) diff --git a/frappe/patches/v8_1/enable_allow_error_traceback_in_system_settings.py b/frappe/patches/v8_1/enable_allow_error_traceback_in_system_settings.py deleted file mode 100644 index 9bd9757a86..0000000000 --- a/frappe/patches/v8_1/enable_allow_error_traceback_in_system_settings.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - """ enable the allow_enable_traceback property in system settings """ - - frappe.reload_doc("core", "doctype", "system_settings") - doc = frappe.get_doc("System Settings", "System Settings") - doc.allow_error_traceback = 1 - doc.flags.ignore_permissions=True - doc.flags.ignore_mandatory=True - doc.save() \ No newline at end of file diff --git a/frappe/patches/v8_1/update_format_options_in_auto_email_report.py b/frappe/patches/v8_1/update_format_options_in_auto_email_report.py deleted file mode 100644 index 56609780cb..0000000000 --- a/frappe/patches/v8_1/update_format_options_in_auto_email_report.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - """ change the XLS option as XLSX in the auto email report """ - - frappe.reload_doc("email", "doctype", "auto_email_report") - - auto_email_list = frappe.get_all("Auto Email Report", filters={"format": "XLS"}) - for auto_email in auto_email_list: - frappe.db.set_value("Auto Email Report", auto_email.name, "format", "XLSX") diff --git a/frappe/patches/v8_10/__init__.py b/frappe/patches/v8_10/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v8_10/delete_static_web_page_from_global_search.py b/frappe/patches/v8_10/delete_static_web_page_from_global_search.py deleted file mode 100644 index 336562c157..0000000000 --- a/frappe/patches/v8_10/delete_static_web_page_from_global_search.py +++ /dev/null @@ -1,5 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.db.sql("""delete from `__global_search` where doctype='Static Web Page'"""); \ No newline at end of file diff --git a/frappe/patches/v8_5/__init__.py b/frappe/patches/v8_5/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py b/frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py deleted file mode 100644 index 89a9a7a1b9..0000000000 --- a/frappe/patches/v8_5/delete_email_group_member_with_invalid_emails.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils import validate_email_address - -def execute(): - ''' update/delete the email group member with the wrong email ''' - - email_group_members = frappe.get_all("Email Group Member", fields=["name", "email"]) - for member in email_group_members: - validated_email = validate_email_address(member.email) - if (validated_email==member.email): - pass - else: - try: - frappe.db.set_value("Email Group Member", member.name, "email", validated_email) - except Exception: - frappe.delete_doc(doctype="Email Group Member", name=member.name, force=1, ignore_permissions=True) \ No newline at end of file diff --git a/frappe/patches/v8_5/patch_event_colors.py b/frappe/patches/v8_5/patch_event_colors.py deleted file mode 100644 index 8ac7aec238..0000000000 --- a/frappe/patches/v8_5/patch_event_colors.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - - if not frappe.db.sql("SHOW COLUMNS FROM `tabEvent` LIKE 'color';"): - return - - colors = ['red', 'green', 'blue', 'yellow', 'skyblue', 'orange'] - hex_colors = ['#ffc4c4', '#cef6d1', '#d2d2ff', '#fffacd', '#d2f1ff', '#ffd2c2'] - - def get_hex_for_color(color): - index = colors.index(color) - return hex_colors[index] - - query = ''' - update tabEvent - set color='{hex}' - where color='{color}' - ''' - - for color in colors: - frappe.db.sql(query.format(color=color, hex=get_hex_for_color(color))) - - frappe.db.commit() diff --git a/frappe/patches/v8_7/__init__.py b/frappe/patches/v8_7/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v8_x/__init__.py b/frappe/patches/v8_x/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/patches/v8_x/update_user_permission.py b/frappe/patches/v8_x/update_user_permission.py deleted file mode 100644 index 693b87c974..0000000000 --- a/frappe/patches/v8_x/update_user_permission.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc('core', 'doctype', 'user_permission') - frappe.delete_doc('core', 'page', 'user-permissions') - for perm in frappe.db.sql(""" - select - name, parent, defkey, defvalue - from - tabDefaultValue - where - parent not in ('__default', '__global') - and - substr(defkey,1,1)!='_' - and - parenttype='User Permission' - """, as_dict=True): - if frappe.db.exists(perm.defkey, perm.defvalue) and frappe.db.exists('User', perm.parent): - frappe.get_doc(dict( - doctype='User Permission', - user=perm.parent, - allow=perm.defkey, - for_value=perm.defvalue, - apply_for_all_roles=0, - )).insert(ignore_permissions = True) - - frappe.db.sql('delete from tabDefaultValue where parenttype="User Permission"') diff --git a/frappe/patches/v9_1/__init__.py b/frappe/patches/v9_1/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/frappe/patches/v9_1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/frappe/patches/v9_1/add_sms_sender_name_as_parameters.py b/frappe/patches/v9_1/add_sms_sender_name_as_parameters.py deleted file mode 100644 index 9d7c0f003f..0000000000 --- a/frappe/patches/v9_1/add_sms_sender_name_as_parameters.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2017, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe - -def execute(): - frappe.reload_doc("core", "doctype", "sms_parameter") - sms_sender_name = frappe.db.get_single_value("SMS Settings", "sms_sender_name") - if sms_sender_name: - frappe.reload_doc("core", "doctype", "sms_settings") - sms_settings = frappe.get_doc("SMS Settings") - sms_settings.append("parameters", { - "parameter": "sender_name", - "value": sms_sender_name - }) - sms_settings.flags.ignore_mandatory = True - sms_settings.flags.ignore_permissions = True - sms_settings.save() diff --git a/frappe/patches/v9_1/move_feed_to_activity_log.py b/frappe/patches/v9_1/move_feed_to_activity_log.py deleted file mode 100644 index db46b4e419..0000000000 --- a/frappe/patches/v9_1/move_feed_to_activity_log.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals -import frappe -from frappe.utils.background_jobs import enqueue - -def execute(): - comm_records_count = frappe.db.count("Communication", {"comment_type": "Updated"}) - if comm_records_count > 100000: - enqueue(method=move_data_from_communication_to_activity_log, queue='short', now=True) - else: - move_data_from_communication_to_activity_log() - -def move_data_from_communication_to_activity_log(): - frappe.reload_doc("core", "doctype", "communication") - frappe.reload_doc("core", "doctype", "activity_log") - - frappe.db.sql("""insert into `tabActivity Log` (name, owner, modified, creation, status, communication_date, - reference_doctype, reference_name, timeline_doctype, timeline_name, link_doctype, link_name, subject, content, user) - select name, owner, modified, creation, status, communication_date, - reference_doctype, reference_name, timeline_doctype, timeline_name, link_doctype, link_name, subject, content, user - from `tabCommunication` - where comment_type = 'Updated'""") - - frappe.db.sql("""delete from `tabCommunication` where comment_type = 'Updated'""") - frappe.delete_doc("DocType", "Authentication Log") \ No newline at end of file diff --git a/frappe/patches/v9_1/resave_domain_settings.py b/frappe/patches/v9_1/resave_domain_settings.py deleted file mode 100644 index 1e54ad3aa5..0000000000 --- a/frappe/patches/v9_1/resave_domain_settings.py +++ /dev/null @@ -1,13 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - domain_settings = frappe.get_doc('Domain Settings') - active_domains = [d.domain for d in domain_settings.active_domains] - try: - for d in ('Education', 'Healthcare', 'Hospitality'): - if d in active_domains and frappe.db.exists('Domain', d): - domain = frappe.get_doc('Domain', d) - domain.setup_domain() - except frappe.LinkValidationError: - pass diff --git a/frappe/patches/v9_1/revert_domain_settings.py b/frappe/patches/v9_1/revert_domain_settings.py deleted file mode 100644 index a14b48dae6..0000000000 --- a/frappe/patches/v9_1/revert_domain_settings.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import unicode_literals -import frappe - -def execute(): - domain_settings = frappe.get_doc('Domain Settings') - active_domains = [d.domain for d in domain_settings.active_domains] - - for domain_name in ('Education', 'Healthcare', 'Hospitality'): - if frappe.db.exists('Domain', domain_name) and domain_name not in active_domains: - domain = frappe.get_doc('Domain', domain_name) - domain.remove_domain() \ No newline at end of file diff --git a/frappe/permissions.py b/frappe/permissions.py index 19f101aab5..07b4a2e68f 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -1,19 +1,15 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt +import copy -from __future__ import unicode_literals, print_function -from six import string_types -import frappe, copy, json +import frappe +import frappe.share from frappe import _, msgprint from frappe.utils import cint -import frappe.share + rights = ("select", "read", "write", "create", "delete", "submit", "cancel", "amend", "print", "email", "report", "import", "export", "set_user_permissions", "share") -# TODO: - -# optimize: meta.get_link_map (check if the doctype link exists for the given permission type) - def check_admin_or_system_manager(user=None): if not user: user = frappe.session.user @@ -58,7 +54,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None, ra meta = frappe.get_meta(doctype) if doc: - if isinstance(doc, string_types): + if isinstance(doc, str): doc = frappe.get_doc(meta.name, doc) perm = get_doc_permissions(doc, user=user, ptype=ptype).get(ptype) if not perm: push_perm_check_log(_('User {0} does not have access to this document').format(frappe.bold(user))) @@ -159,7 +155,7 @@ def get_role_permissions(doctype_meta, user=None, is_owner=None): } } """ - if isinstance(doctype_meta, string_types): + if isinstance(doctype_meta, str): doctype_meta = frappe.get_meta(doctype_meta) # assuming doctype name was passed if not user: user = frappe.session.user @@ -312,7 +308,7 @@ def has_controller_permissions(doc, ptype, user=None): return None def get_doctypes_with_read(): - return list(set([p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()])) + return list({p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()}) def get_valid_perms(doctype=None, user=None): '''Get valid permissions for the current user from DocPerm and Custom DocPerm''' @@ -534,7 +530,7 @@ def get_linked_doctypes(dt): def get_doc_name(doc): if not doc: return None - return doc if isinstance(doc, string_types) else doc.name + return doc if isinstance(doc, str) else doc.name def allow_everything(): ''' diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py index 3a3b14faad..948be60b88 100644 --- a/frappe/printing/doctype/letter_head/letter_head.py +++ b/frappe/printing/doctype/letter_head/letter_head.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.utils import is_image from frappe.model.document import Document @@ -19,7 +18,7 @@ class LetterHead(Document): def validate_disabled_and_default(self): if self.disabled and self.is_default: frappe.throw(_("Letter Head cannot be both disabled and default")) - + if not self.is_default and not self.disabled: if not frappe.db.exists('Letter Head', dict(is_default=1)): self.is_default = 1 diff --git a/frappe/printing/doctype/letter_head/test_letter_head.py b/frappe/printing/doctype/letter_head/test_letter_head.py index b69e9924ea..96dfc68705 100644 --- a/frappe/printing/doctype/letter_head/test_letter_head.py +++ b/frappe/printing/doctype/letter_head/test_letter_head.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/printing/doctype/print_format/print_format.py b/frappe/printing/doctype/print_format/print_format.py index 1c11f2d519..5d4ff92fe2 100644 --- a/frappe/printing/doctype/print_format/print_format.py +++ b/frappe/printing/doctype/print_format/print_format.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import frappe.utils import json diff --git a/frappe/printing/doctype/print_format/test_print_format.py b/frappe/printing/doctype/print_format/test_print_format.py index 121916ae5f..e65eb0183f 100644 --- a/frappe/printing/doctype/print_format/test_print_format.py +++ b/frappe/printing/doctype/print_format/test_print_format.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals, print_function - import frappe import unittest import re diff --git a/frappe/printing/doctype/print_heading/print_heading.py b/frappe/printing/doctype/print_heading/print_heading.py index 1bb3e52dd5..f9955c019d 100644 --- a/frappe/printing/doctype/print_heading/print_heading.py +++ b/frappe/printing/doctype/print_heading/print_heading.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/printing/doctype/print_heading/test_print_heading.py b/frappe/printing/doctype/print_heading/test_print_heading.py index 1a6435e783..ce99cde607 100644 --- a/frappe/printing/doctype/print_heading/test_print_heading.py +++ b/frappe/printing/doctype/print_heading/test_print_heading.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index d64cb4c6d3..31962be050 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -148,7 +148,7 @@ "label": "Print Style" }, { - "default": "Modern", + "default": "Redesign", "fieldname": "print_style", "fieldtype": "Link", "in_list_view": 1, @@ -183,7 +183,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-10-22 23:42:09.471022", + "modified": "2021-02-15 14:16:18.474254", "modified_by": "Administrator", "module": "Printing", "name": "Print Settings", diff --git a/frappe/printing/doctype/print_settings/print_settings.py b/frappe/printing/doctype/print_settings/print_settings.py index cf6a71a8ac..610c083097 100644 --- a/frappe/printing/doctype/print_settings/print_settings.py +++ b/frappe/printing/doctype/print_settings/print_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint diff --git a/frappe/printing/doctype/print_settings/test_print_settings.py b/frappe/printing/doctype/print_settings/test_print_settings.py index b8ad70a681..d1dec861b2 100644 --- a/frappe/printing/doctype/print_settings/test_print_settings.py +++ b/frappe/printing/doctype/print_settings/test_print_settings.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import unittest class TestPrintSettings(unittest.TestCase): diff --git a/frappe/printing/doctype/print_style/print_style.py b/frappe/printing/doctype/print_style/print_style.py index 310babd5df..a91786795c 100644 --- a/frappe/printing/doctype/print_style/print_style.py +++ b/frappe/printing/doctype/print_style/print_style.py @@ -2,7 +2,6 @@ # Copyright (c) 2017, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/printing/doctype/print_style/test_print_style.py b/frappe/printing/doctype/print_style/test_print_style.py index cee57f8826..b717b23df8 100644 --- a/frappe/printing/doctype/print_style/test_print_style.py +++ b/frappe/printing/doctype/print_style/test_print_style.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2017, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest diff --git a/frappe/public/js/frappe/data_import/data_exporter.js b/frappe/public/js/frappe/data_import/data_exporter.js index dee4839b34..03e6288856 100644 --- a/frappe/public/js/frappe/data_import/data_exporter.js +++ b/frappe/public/js/frappe/data_import/data_exporter.js @@ -72,8 +72,8 @@ frappe.data_import.DataExporter = class DataExporter { let child_fieldname = df.fieldname; let label = df.reqd ? // prettier-ignore - __('{0} ({1}) (1 row mandatory)', [df.label || df.fieldname, doctype]) - : __('{0} ({1})', [df.label || df.fieldname, doctype]); + __('{0} ({1}) (1 row mandatory)', [__(df.label || df.fieldname), __(doctype)]) + : __('{0} ({1})', [__(df.label || df.fieldname), __(doctype)]); return { label, fieldname: child_fieldname, diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 46812f5fb6..65c0139b65 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -607,9 +607,7 @@ frappe.Application = class Application { let doc = JSON.parse(pasted_data); if (doc.doctype) { e.preventDefault(); - let sleep = (time) => { - return new Promise((resolve) => setTimeout(resolve, time)); - }; + const sleep = frappe.utils.sleep; frappe.dom.freeze(__('Creating {0}', [doc.doctype]) + '...'); // to avoid abrupt UX diff --git a/frappe/public/js/frappe/form/controls/check.js b/frappe/public/js/frappe/form/controls/check.js index 658ef640a9..1209f52e6d 100644 --- a/frappe/public/js/frappe/form/controls/check.js +++ b/frappe/public/js/frappe/form/controls/check.js @@ -14,8 +14,10 @@ frappe.ui.form.ControlCheck = class ControlCheck extends frappe.ui.form.ControlD `).appendTo(this.parent); } set_input_areas() { - this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0); this.input_area = this.$wrapper.find(".input-area").get(0); + if (this.only_input) return; + + this.label_area = this.label_span = this.$wrapper.find(".label-area").get(0); this.disp_area = this.$wrapper.find(".disp-area").get(0); } make_input() { diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 43bd7443ab..83f3f8dd70 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -141,7 +141,7 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat return $('
  • ') .data('item.autocomplete', d) .prop('aria-selected', 'false') - .html(`

    ${html}

    `) + .html(`

    ${html}

    `) .get(0); }, sort: function() { @@ -200,10 +200,11 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat if(frappe.model.can_create(doctype)) { // new item r.results.push({ - label: "" + html: "" + " " + __("Create a new {0}", [__(me.get_options())]) + "", + label: __("Create a new {0}", [__(me.get_options())]), value: "create_new__link_option", action: me.new_doc }); @@ -213,10 +214,11 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat if (locals && locals['DocType']) { // not applicable in web forms r.results.push({ - label: "" + html: "" + " " + __("Advanced Search") + "", + label: __("Advanced Search"), value: "advanced_search__link_option", action: me.open_advanced_search }); diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 059b0f76f8..99e87c5f21 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -1,7 +1,10 @@ import Quill from 'quill'; import ImageResize from 'quill-image-resize'; +import MagicUrl from 'quill-magic-url'; + Quill.register('modules/imageResize', ImageResize); +Quill.register('modules/magicUrl', MagicUrl); const CodeBlockContainer = Quill.import('formats/code-block-container'); CodeBlockContainer.tagName = 'PRE'; Quill.register(CodeBlockContainer, true); @@ -148,7 +151,8 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for modules: { toolbar: this.get_toolbar_options(), table: true, - imageResize: {} + imageResize: {}, + magicUrl: true }, theme: 'snow' }; diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index c1c95d94cf..6833f68073 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -5,6 +5,7 @@ frappe.ui.form.Dashboard = class FormDashboard { constructor(opts) { $.extend(this, opts); this.setup_dashboard_sections(); + this.set_open_count = frappe.utils.throttle(this.set_open_count, 500); } setup_dashboard_sections() { @@ -178,7 +179,7 @@ frappe.ui.form.Dashboard = class FormDashboard { return; } this.render_links(); - this.set_open_count(); + // this.set_open_count(); show = true; } diff --git a/frappe/public/js/frappe/form/footer/form_timeline.js b/frappe/public/js/frappe/form/footer/form_timeline.js index ab83ed2f71..115a62e098 100644 --- a/frappe/public/js/frappe/form/footer/form_timeline.js +++ b/frappe/public/js/frappe/form/footer/form_timeline.js @@ -190,6 +190,7 @@ class FormTimeline extends BaseTimeline { } doc.owner = doc.sender; doc.user_full_name = doc.sender_full_name; + doc.content = frappe.dom.remove_script_and_style(doc.content); let communication_content = $(frappe.render_template('timeline_message_box', { doc })); if (allow_reply) { this.setup_reply(communication_content, doc); @@ -248,6 +249,7 @@ class FormTimeline extends BaseTimeline { } get_comment_timeline_content(doc) { + doc.content = frappe.dom.remove_script_and_style(doc.content); const comment_content = $(frappe.render_template('timeline_message_box', { doc })); this.setup_comment_actions(comment_content, doc); return comment_content; diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 88d7ceaa94..8064f90a98 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -12,6 +12,7 @@ import './script_manager'; import './script_helpers'; import './sidebar/form_sidebar'; import './footer/footer'; +import './form_tour'; frappe.ui.form.Controller = class FormController { constructor(opts) { @@ -152,6 +153,10 @@ frappe.ui.form.Form = class FrappeForm { parent: $('
    ').insertAfter(this.layout.wrapper.find('.form-message')) }); + this.tour = new frappe.ui.form.FormTour({ + frm: this + }); + // workflow state this.states = new frappe.ui.form.States({ frm: this @@ -175,6 +180,7 @@ frappe.ui.form.Form = class FrappeForm { field && ["Link", "Dynamic Link"].includes(field.df.fieldtype) && field.validate && field.validate(value); me.layout.refresh_dependency(); + me.layout.refresh_sections(); let object = me.script_manager.trigger(fieldname, doc.doctype, doc.name); return object; } @@ -986,7 +992,7 @@ frappe.ui.form.Form = class FrappeForm { } frappe.re_route[frappe.router.get_sub_path()] = `${encodeURIComponent(frappe.router.slug(this.doctype))}/${encodeURIComponent(name)}`; - frappe.set_route('Form', this.doctype, name); + !frappe._from_link && frappe.set_route('Form', this.doctype, name); } // ACTIONS @@ -1068,7 +1074,7 @@ frappe.ui.form.Form = class FrappeForm { if(!this.doc.__islocal) { frappe.model.remove_from_locals(this.doctype, this.docname); - frappe.model.with_doc(this.doctype, this.docname, () => { + return frappe.model.with_doc(this.doctype, this.docname, () => { this.refresh(); }); } @@ -1078,6 +1084,7 @@ frappe.ui.form.Form = class FrappeForm { if (this.fields_dict[fname] && this.fields_dict[fname].refresh) { this.fields_dict[fname].refresh(); this.layout.refresh_dependency(); + this.layout.refresh_sections(); } } @@ -1604,45 +1611,6 @@ frappe.ui.form.Form = class FrappeForm { }, 1000); } - show_tour(on_finish) { - if (!Array.isArray(frappe.tour[this.doctype])) { - return; - } - - const driver = new frappe.Driver({ - className: 'frappe-driver', - allowClose: false, - padding: 10, - overlayClickNext: true, - keyboardControl: true, - nextBtnText: 'Next', - prevBtnText: 'Previous', - opacity: 0.25, - onNext: () => { - if (!driver.hasNextStep()) { - on_finish && on_finish(); - } - } - }); - - this.layout.sections.forEach(section => section.collapse(false)); - - let steps = frappe.tour[this.doctype].map(step => { - let field = this.get_docfield(step.fieldname); - return { - element: `.frappe-control[data-fieldname='${step.fieldname}']`, - popover: { - title: step.title || field.label, - description: step.description - } - }; - }); - - driver.defineSteps(steps); - frappe.router.on('change', () => driver.reset()); - driver.start(); - } - setup_docinfo_change_listener() { let doctype = this.doctype; let docname = this.docname; diff --git a/frappe/public/js/frappe/form/form_tour.js b/frappe/public/js/frappe/form/form_tour.js new file mode 100644 index 0000000000..7f7ec9ce4f --- /dev/null +++ b/frappe/public/js/frappe/form/form_tour.js @@ -0,0 +1,252 @@ +frappe.ui.form.FormTour = class FormTour { + constructor({ frm }) { + this.frm = frm; + this.driver_steps = []; + + this.init_driver(); + } + + init_driver() { + this.driver = new frappe.Driver({ + className: 'frappe-driver', + allowClose: false, + padding: 10, + overlayClickNext: true, + keyboardControl: true, + nextBtnText: 'Next', + prevBtnText: 'Previous', + opacity: 0.25, + onHighlighted: (step) => { + // if last step is to save, then attach a listener to save button + if (step.options.is_save_step) { + $(step.options.element).one('click', () => this.driver.reset()); + } + + // focus on input + const $input = $(step.node).find('input').get(0); + if ($input) + frappe.utils.sleep(200).then(() => $input.focus()); + } + }); + + frappe.router.on('change', () => this.driver.reset()); + this.frm.layout.sections.forEach(section => section.collapse(false)); + } + + async init({ tour_name, on_finish }) { + if (tour_name) { + this.tour = await frappe.db.get_doc('Form Tour', tour_name); + } else { + this.tour = { steps: frappe.tour[this.frm.doctype] }; + } + + if (on_finish) this.on_finish = on_finish; + + this.build_steps(); + this.update_driver_steps(); + } + + build_steps() { + this.driver_steps = []; + this.tour.steps.forEach((step) => { + const on_next = () => { + if (!this.is_next_condition_satisfied(step)) { + this.driver.preventMove(); + } + + if (!this.driver.hasNextStep()) { + this.on_finish && this.on_finish(); + } + }; + + const driver_step = this.get_step(step, on_next); + this.driver_steps.push(driver_step); + + if (step.fieldtype == 'Table') this.handle_table_step(step); + if (step.is_table_field) this.handle_child_table_step(step); + }); + + if (this.tour.save_on_complete) { + this.add_step_to_save(); + } + } + + is_next_condition_satisfied(step) { + const form = step.is_table_field ? this.frm.cur_grid.grid_form : this.frm; + return form.layout.evaluate_depends_on_value(step.next_step_condition || true); + } + + get_step(step_info, on_next) { + const { name, fieldname, title, description, position, is_table_field } = step_info; + const field = this.frm.get_field(fieldname); + let element = field ? field.wrapper : `.frappe-control[data-fieldname='${fieldname}']`; + + if (is_table_field) { + element = `.grid-row-open .frappe-control[data-fieldname='${fieldname}']`; + } + + return { + element, + name, + popover: { title, description, position: frappe.router.slug(position) }, + onNext: on_next + }; + } + + update_driver_steps(steps = []) { + if (steps.length == 0) { + steps = this.driver_steps; + } + this.driver.defineSteps(steps); + } + + start(idx = 0) { + if (this.driver_steps.length == 0) { + return; + } + this.driver.start(idx); + } + + get_next_step() { + // returns the next step only if driver is active + if (this.driver.isActivated & this.driver.hasNextStep()) { + const current_step = this.driver.currentStep; + return this.driver.steps[current_step + 1]; + } + return; + } + + handle_table_step(step_info) { + const is_last_step = step_info.idx == this.tour.steps.length; + + if (!is_last_step) { + // if next step field is inside currently highlighted table field + // then check if there is a row -> if not, then prompt to add row + // then edit the first row and hightlight next step + + const curr_step = step_info; + const next_step = this.tour.steps[curr_step.idx]; + const is_next_field_in_curr_table = next_step.parent_field == curr_step.field; + + if (!is_next_field_in_curr_table) return; + + const rows = this.frm.doc[curr_step.fieldname]; + const table_has_rows = rows && rows.length > 0; + if (table_has_rows) { + // table already has rows + // then just edit the first one on next step + const curr_driver_step = this.driver_steps.find(s => s.name == curr_step.name); + curr_driver_step.onNext = () => { + if (this.is_next_condition_satisfied(curr_step)) { + this.expand_row_and_proceed(curr_step, curr_step.idx); + } else { + this.driver.preventMove(); + } + }; + this.update_driver_steps(); + + } else { + this.add_new_row_step(curr_step); + } + } + } + + add_new_row_step(step) { + const $add_row = `.frappe-control[data-fieldname='${step.fieldname}'] .grid-add-row`; + const add_row_step = { + element: $add_row, + popover: { title: __("Add a Row"), description: "" }, + onNext: () => { + if (!cur_frm.cur_grid) { + this.driver.preventMove(); + } + } + }; + this.driver_steps.push(add_row_step); + + // setup a listener on add row button + // so, once the row is added, move to next step automatically + $($add_row).one('click', () => { + this.expand_row_and_proceed(step, step.idx + 1); // +1 since add row step is added + }); + } + + expand_row_and_proceed(step, start_from) { + this.open_first_row_of(step.fieldname); + this.update_driver_steps(); // need to define again, since driver.js only considers steps which are inside DOM + frappe.utils.sleep(300).then(() => this.driver.start(start_from)); + } + + open_first_row_of(fieldname) { + this.frm.fields_dict[fieldname].grid.grid_rows[0].toggle_view(); + + // setup a listener on close row button + // so, once the row is closed, move to next step automatically + const $close_row = '.grid-row-open .grid-collapse-row'; + $($close_row).one('click', () => { + const next_step = this.get_next_step(); + const next_element = next_step.options.is_save_step ? null : next_step.node; + + frappe.utils.scroll_to(next_element, true, 150, null, () => { + this.driver.moveNext(); + frappe.flags.disable_auto_scroll = false; + }); + frappe.flags.disable_auto_scroll = true; + }); + } + + handle_child_table_step(step_info) { + const is_last_step = step_info.idx == this.tour.steps.length; + + if (!is_last_step) { + const curr_step = step_info; + const next_step = this.tour.steps[curr_step.idx]; + const field = this.frm.get_field(next_step.fieldname); + + if (!field) return; + + // next step highlights parent field + // so, add a step to prompt user to collapse grid form + this.add_collapse_row_step(); + + } else if (this.tour.save_on_complete) { + // if last step & save on complete is checked + // add a step to prompt user to collapse grid form + // to be able to save as a last step + this.add_collapse_row_step(); + } + } + + add_collapse_row_step() { + const $close_row = '.grid-row-open .grid-collapse-row'; + const close_row_step = { + element: $close_row, + popover: { title: __("Collapse"), description: "", position: "left" }, + onNext: () => { + if (cur_frm.cur_grid) { + this.driver.preventMove(); + } + } + }; + this.driver_steps.push(close_row_step); + } + + add_step_to_save() { + const page_id = `#page-${this.frm.doctype}`; + const $save_btn = `${page_id} .standard-actions .primary-action`; + const save_step = { + element: $save_btn, + is_save_step: true, + allowClose: false, + overlayClickNext: false, + popover: { + title: __("Save"), + description: "", + position: "left", + doneBtnText: __("Save") + } + }; + this.driver_steps.push(save_step); + frappe.ui.form.on(this.frm.doctype, 'after_save', () => this.on_finish && this.on_finish()); + } +}; \ No newline at end of file diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 89c34ed80c..b9a838688d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -221,9 +221,13 @@ frappe.form.formatters = { Tag: function(value) { var html = ""; $.each((value || "").split(","), function(i, v) { - if(v) html+= ''+v +''; + if (v) html += ` + + ${v} + `; }); return html; }, @@ -310,6 +314,7 @@ frappe.form.get_formatter = function(fieldtype) { frappe.format = function(value, df, options, doc) { if(!df) df = {"fieldtype":"Data"}; + if (df.fieldname == '_user_tags') df.fieldtype = 'Tag'; var fieldtype = df.fieldtype || "Data"; // format Dynamic Link as a Link diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js index dd7c339395..ebc3fa19f5 100644 --- a/frappe/public/js/frappe/form/grid.js +++ b/frappe/public/js/frappe/form/grid.js @@ -196,7 +196,7 @@ export default class Grid { tasks.push(() => { if (dirty) { this.refresh(); - this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); + this.frm && this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); } }); @@ -210,9 +210,9 @@ export default class Grid { delete_all_rows() { frappe.confirm(__("Are you sure you want to delete all rows?"), () => { - this.frm.doc[this.df.fieldname] = []; - $(this.parent).find('.rows').empty(); - this.grid_rows = []; + this.grid_rows.forEach(row => { + row.remove(); + }); this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); this.wrapper.find('.grid-heading-row .grid-row-check:checked:first').prop('checked', 0); @@ -236,6 +236,10 @@ export default class Grid { } refresh_remove_rows_button() { + if (this.df.cannot_delete_rows) { + return; + } + this.remove_rows_button.toggleClass('hidden', this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true); this.remove_all_rows_button.toggleClass('hidden', @@ -345,6 +349,9 @@ export default class Grid { if (d.idx === undefined) { d.idx = ri + 1; } + if (d.name === undefined) { + d.name = "row " + d.idx; + } if (this.grid_rows[ri] && !append_row) { var grid_row = this.grid_rows[ri]; grid_row.doc = d; diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 7eea578f8c..8c51314066 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -529,7 +529,7 @@ export default class GridRow { // hide other var open_row = this.get_open_form(); - if (show===undefined) show = !!!open_row; + if (show === undefined) show = !open_row; // call blur document.activeElement && document.activeElement.blur(); @@ -569,6 +569,9 @@ export default class GridRow { .find('.grid-insert-row-below, .grid-insert-row, .grid-duplicate-row, .grid-append-row') .toggle(!cannot_add_rows); + this.wrapper.find('.grid-delete-row') + .toggle(!(this.grid.df && this.grid.df.cannot_delete_rows)); + frappe.dom.freeze("", "dark"); if (cur_frm) cur_frm.cur_grid = this; this.wrapper.addClass("grid-row-open"); @@ -594,19 +597,42 @@ export default class GridRow { this.wrapper.removeClass("grid-row-open"); } open_prev() { - const row_index = this.wrapper.index(); - if (this.grid.grid_rows[row_index - 1]) { - this.grid.grid_rows[row_index - 1].toggle_view(true); - } + if (!this.doc) return; + this.open_row_at_index(this.doc.idx - 2); } open_next() { - const row_index = this.wrapper.index(); - if (this.grid.grid_rows[row_index + 1]) { - this.grid.grid_rows[row_index + 1].toggle_view(true); - } else { + if (!this.doc) return; + + if (!this.open_row_at_index(this.doc.idx)) { this.grid.add_new_row(null, null, true); } } + open_row_at_index(row_index) { + if (!this.grid.data[row_index]) return; + + this.change_page_if_reqd(row_index); + this.grid.grid_rows[row_index].toggle_view(true); + return true; + } + change_page_if_reqd(row_index) { + const { + page_index, + page_length + } = this.grid.grid_pagination; + + row_index++; + let new_page; + + if (row_index <= (page_index - 1) * page_length) { + new_page = page_index - 1; + } else if (row_index > page_index * page_length) { + new_page = page_index + 1; + } + + if (new_page) { + this.grid.grid_pagination.go_to_page(new_page); + } + } refresh_field(fieldname, txt) { let df = this.docfields.find(col => { return col.fieldname === fieldname; diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 282655b589..045e5dc1b3 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -543,7 +543,7 @@ frappe.ui.form.Layout = class Layout { } else if (expression.substr(0, 5)=='eval:') { try { - out = frappe.utils.eval(expression.substr(5), { doc }); + out = frappe.utils.eval(expression.substr(5), { doc, parent }); if (parent && parent.istable && expression.includes('is_submittable')) { out = true; } diff --git a/frappe/public/js/frappe/form/sidebar/attachments.js b/frappe/public/js/frappe/form/sidebar/attachments.js index 4f116df63e..538534e5cf 100644 --- a/frappe/public/js/frappe/form/sidebar/attachments.js +++ b/frappe/public/js/frappe/form/sidebar/attachments.js @@ -11,7 +11,7 @@ frappe.ui.form.Attachments = class Attachments { this.parent.find(".add-attachment-btn").click(function() { me.new_attachment(); }); - this.add_attachment_wrapper = this.parent.find(".add_attachment").parent(); + this.add_attachment_wrapper = this.parent.find(".add-attachment-btn"); this.attachments_label = this.parent.find(".attachments-label"); } max_reached(raise_exception=false) { @@ -39,7 +39,7 @@ frappe.ui.form.Attachments = class Attachments { this.parent.find(".attachment-row").remove(); var max_reached = this.max_reached(); - this.add_attachment_wrapper.toggleClass("hide", !max_reached); + this.add_attachment_wrapper.toggle(!max_reached); // add attachment objects var attachments = this.get_attachments(); diff --git a/frappe/public/js/frappe/ui/chart.js b/frappe/public/js/frappe/ui/chart.js index 6df28204f9..36e081a7f0 100644 --- a/frappe/public/js/frappe/ui/chart.js +++ b/frappe/public/js/frappe/ui/chart.js @@ -1,3 +1,39 @@ import { Chart } from "frappe-charts/dist/frappe-charts.esm"; +frappe.provide("frappe.ui"); frappe.Chart = Chart; + +frappe.ui.RealtimeChart = class RealtimeChart extends frappe.Chart { + constructor(element, socketEvent, maxLabelPoints = 8, data) { + super(element, data); + if (data.data.datasets[0].values.length > maxLabelPoints) { + frappe.throw( + __( + "Length of passed data array is greater than value of maximum allowed label points!" + ) + ); + } + this.currentSize = data.data.datasets[0].values.length; + this.socketEvent = socketEvent; + this.maxLabelPoints = maxLabelPoints; + + this.start_updating = function() { + frappe.realtime.on(this.socketEvent, data => { + this.update_chart(data.label, data.points); + }); + }; + + this.stop_updating = function() { + frappe.realtime.off(this.socketEvent); + }; + + this.update_chart = function(label, data) { + if (this.currentSize >= this.maxLabelPoints) { + this.removeDataPoint(0); + } else { + this.currentSize++; + } + this.addDataPoint(label, data); + }; + } +}; diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 611ab024bf..72312d7f13 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -323,9 +323,12 @@ frappe.ui.FilterGroup = class { } add_filters_to_filter_group(filters) { - filters.forEach((filter) => { - this.add_filter(filter[0], filter[1], filter[2], filter[3]); - }); + if (filters.length) { + this.toggle_empty_filters(false); + filters.forEach((filter) => { + this.add_filter(filter[0], filter[1], filter[2], filter[3]); + }); + } } add(filters, refresh = true) { diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 3ebf9c9d3d..692d675c62 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -381,10 +381,11 @@ frappe.ui.GroupBy = class { this.group_by_fields = {}; this.all_fields = {}; - let fields = this.report_view.meta.fields.filter((f) => + const fields = this.report_view.meta.fields.filter((f) => ['Select', 'Link', 'Data', 'Int', 'Check'].includes(f.fieldtype) ); - this.group_by_fields[this.doctype] = fields; + const tag_field = {fieldname: '_user_tags', fieldtype: 'Data', label: __('Tags')}; + this.group_by_fields[this.doctype] = fields.concat(tag_field); this.all_fields[this.doctype] = this.report_view.meta.fields; const standard_fields_filter = (df) => diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 2e8ba7d206..067fed233c 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -26,13 +26,13 @@ frappe.throw = function(msg) { frappe.confirm = function(message, confirm_action, reject_action) { var d = new frappe.ui.Dialog({ - title: __("Confirm"), - primary_action_label: __("Yes"), + title: __("Confirm", null, "Title of confirmation dialog"), + primary_action_label: __("Yes", null, "Approve confirmation dialog"), primary_action: () => { confirm_action && confirm_action(); d.hide(); }, - secondary_action_label: __("No"), + secondary_action_label: __("No", null, "Dismiss confirmation dialog"), secondary_action: () => d.hide(), }); @@ -88,9 +88,9 @@ frappe.prompt = function(fields, callback, title, primary_label) { if(!$.isArray(fields)) fields = [fields]; var d = new frappe.ui.Dialog({ fields: fields, - title: title || __("Enter Value"), + title: title || __("Enter Value", null, "Title of prompt dialog"), }); - d.set_primary_action(primary_label || __("Submit"), function() { + d.set_primary_action(primary_label || __("Submit", null, "Primary action of prompt dialog"), function() { var values = d.get_values(); if(!values) { return; diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index e740718ef9..22fdf476b8 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -377,11 +377,12 @@ frappe.ui.Page = class Page { }); } - add_actions_menu_item(label, click, standard) { + add_actions_menu_item(label, click, standard, shortcut) { return this.add_dropdown_item({ label, click, standard, + shortcut, parent: this.actions, show_parent: false }); @@ -409,6 +410,9 @@ frappe.ui.Page = class Page { parent.parent().removeClass("hide"); } + let $link = this.is_in_group_button_dropdown(parent, 'li > a.grey-link', label); + if ($link) return $link; + let $li; let $icon = ``; @@ -440,9 +444,8 @@ frappe.ui.Page = class Page { `); } - var $link = $li.find("a").on("click", click); - if (this.is_in_group_button_dropdown(parent, 'li > a.grey-link', label)) return; + $link = $li.find("a").on("click", click); if (standard) { $li.appendTo(parent); @@ -508,7 +511,7 @@ frappe.ui.Page = class Page { let item = $(this).html(); return $(item).attr('data-label') === label; }); - return result.length > 0; + return result.length > 0 && result; } clear_btn_group(parent) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 484d9c65f1..8d3d0edd53 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -268,7 +268,9 @@ Object.assign(frappe.utils, {

    '); return content.html(); }, - scroll_to: function(element, animate=true, additional_offset, element_to_be_scrolled) { + scroll_to: function(element, animate=true, additional_offset, element_to_be_scrolled, callback) { + if (frappe.flags.disable_auto_scroll) return; + element_to_be_scrolled = element_to_be_scrolled || $("html, body"); let scroll_top = 0; if (element) { @@ -289,7 +291,7 @@ Object.assign(frappe.utils, { } if (animate) { - element_to_be_scrolled.animate({ scrollTop: scroll_top }); + element_to_be_scrolled.animate({ scrollTop: scroll_top }).promise().then(callback); } else { element_to_be_scrolled.scrollTop(scroll_top); } @@ -1319,5 +1321,9 @@ Object.assign(frappe.utils, { let e = clipboard_paste_event; let clipboard_data = e.clipboardData || window.clipboardData || e.originalEvent.clipboardData; return clipboard_data.getData('Text'); + }, + + sleep(time) { + return new Promise((resolve) => setTimeout(resolve, time)); } }); diff --git a/frappe/public/js/frappe/views/calendar/calendar.js b/frappe/public/js/frappe/views/calendar/calendar.js index 87f692d125..0ab5e2e7dc 100644 --- a/frappe/public/js/frappe/views/calendar/calendar.js +++ b/frappe/public/js/frappe/views/calendar/calendar.js @@ -109,7 +109,7 @@ frappe.views.CalendarView = class CalendarView extends frappe.views.ListView { frappe.views.Calendar = class Calendar { constructor(options) { $.extend(this, options); - this.field_map = { + this.field_map = this.field_map || { "id": "name", "start": "start", "end": "end", diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index f65f1a83c2..0eeb5d9ffc 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -145,7 +145,7 @@ frappe.views.CommunicationComposer = class { ); }); - if (email_accounts.length > 1) { + if (email_accounts.length) { fields.unshift({ label: __("From"), fieldtype: "Select", @@ -724,9 +724,14 @@ frappe.views.CommunicationComposer = class { } message += await this.get_signature(); - if (this.real_name && !message.includes("")) { - message = `

    ${__('Dear')} ${this.real_name},

    -
    ${message}`; + + const SALUTATION_END_COMMENT = ""; + if (this.real_name && !message.includes(SALUTATION_END_COMMENT)) { + this.message = ` +

    ${__('Dear {0},', [this.real_name], 'Salutation in new email')},

    + ${SALUTATION_END_COMMENT}
    + ${message} + `; } if (this.is_a_reply) { diff --git a/frappe/public/js/frappe/views/kanban/kanban_board.js b/frappe/public/js/frappe/views/kanban/kanban_board.js index 50f911c808..1d7ea37fe6 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_board.js +++ b/frappe/public/js/frappe/views/kanban/kanban_board.js @@ -180,7 +180,7 @@ frappe.provide("frappe.views"); method_name = "update_order_for_single_card"; args = { board_name: this.board.name, - docname: unescape(card.name), + docname: card.name, from_colname: card.from_colname, to_colname: card.to_colname, old_index: card.old_index, @@ -222,7 +222,7 @@ frappe.provide("frappe.views"); var col_name = $(this).data().columnValue; order[col_name] = []; $(this).find('.kanban-card-wrapper').each(function() { - var card_name = unescape($(this).data().name); + var card_name = decodeURIComponent($(this).data().name); order[col_name].push(card_name); }); }); @@ -514,7 +514,7 @@ frappe.provide("frappe.views"); wrapper.find('.kanban-cards').height('auto'); // update order const args = { - name: $(e.item).attr('data-name'), + name: decodeURIComponent($(e.item).attr('data-name')), from_colname: $(e.from).parents('.kanban-column').attr('data-column-value'), to_colname: $(e.to).parents('.kanban-column').attr('data-column-value'), old_index: e.oldIndex, diff --git a/frappe/public/js/frappe/views/kanban/kanban_card.html b/frappe/public/js/frappe/views/kanban/kanban_card.html index b854b88d18..88cf366ccc 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_card.html +++ b/frappe/public/js/frappe/views/kanban/kanban_card.html @@ -1,4 +1,4 @@ -
    +
    {% if(image_url) { %} diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index b29b6b87e6..6a324f6034 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -410,7 +410,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { x_fields.push({ label: col.content, fieldname: col.id, - value: col.id, + value: col.id, }); // numeric values in y @@ -1024,8 +1024,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { return docfield.fieldtype === 'Date' ? 'right' : 'left'; })(); + let id = fieldname; + // child table column - const id = doctype !== this.doctype ? `${doctype}:${fieldname}` : fieldname; + if (doctype !== this.doctype && fieldname !== '_aggregate_column') { + id = `${doctype}:${fieldname}`; + } let width = (docfield ? cint(docfield.width) : null) || null; if (this.report_doc) { diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index e552a7dd55..b487c0134f 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -203,7 +203,7 @@ export default class OnboardingWidget extends Widget { frappe.route_hooks = {}; frappe.route_hooks.after_load = (frm) => { - frm.show_tour(() => { + const on_finish = () => { let msg_dialog = frappe.msgprint({ message: __("Let's take you back to onboarding"), title: __("Great Job"), @@ -217,7 +217,10 @@ export default class OnboardingWidget extends Widget { label: () => __("Continue"), }, }); - }); + }; + frm.tour + .init({ on_finish }) + .then(() => frm.tour.start()); }; frappe.set_route(route); @@ -290,12 +293,15 @@ export default class OnboardingWidget extends Widget { frappe.route_hooks = {}; frappe.route_hooks.after_load = (frm) => { - frm.show_tour(() => { + const on_finish = () => { frappe.msgprint({ message: __("Awesome, now try making an entry yourself"), title: __("Great"), }); - }); + }; + frm.tour + .init({ on_finish }) + .then(() => frm.tour.start()); }; let callback = () => { diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index eefb78c29a..3f5a4acd73 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -271,18 +271,19 @@ class ShortcutDialog extends WidgetDialog { } process_data(data) { - let stats_filter = {}; if (this.dialog.get_value("type") == "DocType" && this.filter_group) { let filters = this.filter_group.get_filters(); + let stats_filter = null; if (filters.length) { + stats_filter = {}; filters.forEach((arr) => { stats_filter[arr[1]] = [arr[2], arr[3]]; }); - - data.stats_filter = JSON.stringify(stats_filter); + stats_filter = JSON.stringify(stats_filter); } + data.stats_filter = stats_filter; } data.label = data.label diff --git a/frappe/public/scss/common/awesomeplete.scss b/frappe/public/scss/common/awesomeplete.scss index 096da1e2fd..b9e8035d68 100644 --- a/frappe/public/scss/common/awesomeplete.scss +++ b/frappe/public/scss/common/awesomeplete.scss @@ -31,6 +31,7 @@ margin: 0; padding: var(--padding-xs); z-index: 1; + min-width: 250px; &> li { cursor: pointer; @@ -44,6 +45,8 @@ } p { margin-bottom: 0; + overflow-y: clip; + text-overflow: ellipsis; } } diff --git a/frappe/public/scss/common/buttons.scss b/frappe/public/scss/common/buttons.scss index 591dc5bba6..de3a4cfc20 100644 --- a/frappe/public/scss/common/buttons.scss +++ b/frappe/public/scss/common/buttons.scss @@ -48,13 +48,13 @@ $active-border: darken($primary-light, 12.5%) ); - color: var(--blue-500); + color: var(--primary); &:hover, &:active { - color: var(--blue-500); + color: var(--primary); } &:focus { - box-shadow: 0 0 0 0.2rem var(--blue-50) + box-shadow: 0 0 0 0.2rem var(--primary-light); } } @@ -77,11 +77,11 @@ } .btn.btn-primary { - background-color: var(--primary-color); + background-color: var(--primary); color: var(--white); white-space: nowrap; --icon-stroke: currentColor; - --icon-fill-bg: var(--primary-color); + --icon-fill-bg: var(--primary); } .btn.btn-danger { diff --git a/frappe/public/scss/common/controls.scss b/frappe/public/scss/common/controls.scss index 83fc4461d6..c939c6de39 100644 --- a/frappe/public/scss/common/controls.scss +++ b/frappe/public/scss/common/controls.scss @@ -241,6 +241,7 @@ textarea.form-control { // rating .rating { + cursor: pointer; --star-fill: var(--gray-300); .star-hover { --star-fill: var(--yellow-100); @@ -248,6 +249,24 @@ textarea.form-control { .star-click { --star-fill: var(--yellow-300); } + + .rating-box { + background-color: var(--gray-300); + border-radius: 5px; + font-size: 14px; + text-align: center; + padding: 2px; + cursor: pointer; + width: 25px; + height: 25px; + margin: 4px 2px; + } + .rating-hover { + background-color: var(--yellow-100); + } + .rating-click { + background-color: var(--yellow-300); + } } .frappe-control .control-value { diff --git a/frappe/public/scss/common/css_variables.scss b/frappe/public/scss/common/css_variables.scss index 7058fdc42c..48a8a48f5f 100644 --- a/frappe/public/scss/common/css_variables.scss +++ b/frappe/public/scss/common/css_variables.scss @@ -24,7 +24,7 @@ --blue-100: #D3E9FC; --blue-50 : #F0F8FE; - --cyan-900: #006464; + --cyan-900: #006464; --cyan-800: #007272; --cyan-700: #008b8b; --cyan-600: #02c5c5; @@ -179,6 +179,10 @@ --text-on-pink: var(--pink-500); --text-on-cyan: var(--cyan-600); + --disabled-control-bg: var(--gray-50); + --control-bg: var(--gray-100); + --control-bg-on-gray: var(--gray-200); + --awesomplete-hover-bg: var(--control-bg); // Other Colors @@ -204,4 +208,8 @@ --primary-color: #2490EF; --btn-height: 28px; + // Checkbox + --checkbox-right-margin: var(--margin-xs); + --checkbox-size: 14px; + --checkbox-focus-shadow: 0 0 0 2px var(--gray-300); } diff --git a/frappe/public/scss/common/datepicker.scss b/frappe/public/scss/common/datepicker.scss index 93bdfcc03d..b21eb58caf 100644 --- a/frappe/public/scss/common/datepicker.scss +++ b/frappe/public/scss/common/datepicker.scss @@ -15,6 +15,10 @@ &--time-current-hours, &--time-current-minutes, &--time-current-seconds { font-family: inherit; + &:after { + color: var(--text-color); + background-color: var(--fg-hover-color); + } } &--day-name { @@ -47,10 +51,13 @@ background: fade(#0089FF, 10%); } + &.-focus- { + background-color: var(--fg-hover-color); + } + &.-selected-.-focus- { background: fade(#0089FF, 90%); } - } &--time, &--buttons { @@ -67,8 +74,20 @@ &--time-row:first-child { margin: 0; } + + &--pointer { + background: var(--fg-color); + border-top-right-radius: 2px; + border: 1px var(--border-color); + border-style: solid solid hidden hidden; + } + + &--button { + color: var(--brand-color); + &:hover { + color: var(--text-color); + background-color: var(--fg-hover-color); + } + } } -.datepicker--button { - color: var(--brand-color); -} diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index 3cc5139d9e..57d0583b35 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -53,7 +53,12 @@ display: none; } +.form-grid .grid-heading-row .template-row { + margin-left: 20px; +} + .form-grid .template-row { + width: calc(100% - 30px); padding: 8px 15px; } @@ -140,7 +145,7 @@ .checkbox { margin: 0px; text-align: center; - margin-top: 9px; + margin-top: var(--padding-sm); } textarea { diff --git a/frappe/public/scss/common/quill.scss b/frappe/public/scss/common/quill.scss index 6f6e09dc70..a7a690543b 100644 --- a/frappe/public/scss/common/quill.scss +++ b/frappe/public/scss/common/quill.scss @@ -35,13 +35,14 @@ .ql-container.ql-snow { border-bottom-left-radius: var(--border-radius); border-bottom-right-radius: var(--border-radius); - overflow: hidden; } .ql-snow { .ql-editor { min-height: 400px; max-height: 600px; + border-bottom-left-radius: var(--border-radius); + border-bottom-right-radius: var(--border-radius); } .ql-stroke { stroke: var(--icon-stroke); @@ -176,6 +177,7 @@ } .ql-editor.read-mode { + height: unset; padding: 0; .mention { --user-mention-bg-color: var(--control-bg); diff --git a/frappe/public/scss/desk/driver.scss b/frappe/public/scss/desk/driver.scss index ddf594393f..4135d9667b 100644 --- a/frappe/public/scss/desk/driver.scss +++ b/frappe/public/scss/desk/driver.scss @@ -64,4 +64,16 @@ div#driver-popover-item { input.driver-highlighted-element { background-color: var(--fg-color); +} + +.driver-fix-stacking { + z-index: auto !important; + position: unset !important; + opacity: 1.0 !important; + transform: none !important; + filter: none !important; + perspective: none !important; + transform-style: flat !important; + transform-box: border-box !important; + will-change: unset !important; } \ No newline at end of file diff --git a/frappe/public/scss/website/blog.scss b/frappe/public/scss/website/blog.scss index 9918b490c5..ea82efed21 100644 --- a/frappe/public/scss/website/blog.scss +++ b/frappe/public/scss/website/blog.scss @@ -14,6 +14,10 @@ position: relative; width: 100%; + .card { + border: 1px solid var(--border-color) + } + .card-body { display: flex; flex-direction: column; diff --git a/frappe/public/scss/website/css_variables.scss b/frappe/public/scss/website/css_variables.scss index 2aeb842802..463a0db49e 100644 --- a/frappe/public/scss/website/css_variables.scss +++ b/frappe/public/scss/website/css_variables.scss @@ -28,4 +28,6 @@ --font-size-4xl: #{$font-size-4xl}; --font-size-5xl: #{$font-size-5xl}; --font-size-6xl: #{$font-size-6xl}; + + --card-border-radius: #{$card-border-radius}; } diff --git a/frappe/public/scss/website/markdown.scss b/frappe/public/scss/website/markdown.scss index c5f44d20d8..6f009df393 100644 --- a/frappe/public/scss/website/markdown.scss +++ b/frappe/public/scss/website/markdown.scss @@ -48,7 +48,6 @@ $font-sizes-mobile: ( } li { - text-indent: 0.25rem; padding-top: 1px; padding-bottom: 1px; } diff --git a/frappe/public/scss/website/sidebar.scss b/frappe/public/scss/website/sidebar.scss index b13eaf2a74..76956c9136 100644 --- a/frappe/public/scss/website/sidebar.scss +++ b/frappe/public/scss/website/sidebar.scss @@ -10,7 +10,7 @@ margin-top: 0.25rem; border-radius: 0.375rem; font-size: $font-size-sm; - color: $gray-600; + color: var(--text-color); text-decoration: none; font-weight: 500; @include transition(); @@ -26,8 +26,8 @@ } .sidebar-item a.active { - color: $primary; - background-color: $primary-light; + color: var(--primary); + background-color: var(--primary-light); } .sidebar-item-icon { diff --git a/frappe/pythonrc.py b/frappe/pythonrc.py index 308bad7382..6761ead05b 100755 --- a/frappe/pythonrc.py +++ b/frappe/pythonrc.py @@ -3,8 +3,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import os import frappe frappe.connect(site=os.environ.get("site")) \ No newline at end of file diff --git a/frappe/rate_limiter.py b/frappe/rate_limiter.py index 40db8fe892..023cdb9cb0 100644 --- a/frappe/rate_limiter.py +++ b/frappe/rate_limiter.py @@ -2,8 +2,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - from datetime import datetime from functools import wraps from typing import Union, Callable diff --git a/frappe/recorder.py b/frappe/recorder.py index e3eef94809..02036b7374 100644 --- a/frappe/recorder.py +++ b/frappe/recorder.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - from collections import Counter import datetime import inspect diff --git a/frappe/search/website_search.py b/frappe/search/website_search.py index 452ea2a427..49bdade936 100644 --- a/frappe/search/website_search.py +++ b/frappe/search/website_search.py @@ -9,7 +9,7 @@ from whoosh.fields import ID, TEXT, Schema import frappe from frappe.search.full_text_search import FullTextSearch from frappe.utils import set_request, update_progress_bar -from frappe.website.render import render_page +from frappe.website.serve import get_response_content INDEX_NAME = "web_routes" @@ -61,7 +61,7 @@ class WebsiteSearch(FullTextSearch): try: set_request(method="GET", path=route) - content = render_page(route) + content = get_response_content(route) soup = BeautifulSoup(content, "html.parser") page_content = soup.find(class_="page_content") text_content = page_content.text if page_content else "" diff --git a/frappe/sessions.py b/frappe/sessions.py index 9e503bbe25..4d922d6769 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals """ Boot session from cache or build @@ -16,8 +14,7 @@ import frappe.model.meta import frappe.defaults import frappe.translate import redis -from six.moves.urllib.parse import unquote -from six import text_type +from urllib.parse import unquote from frappe.cache_manager import clear_user_cache @frappe.whitelist(allow_guest=True) @@ -170,7 +167,8 @@ def get_csrf_token(): def generate_csrf_token(): frappe.local.session.data.csrf_token = frappe.generate_hash() - frappe.local.session_obj.update(force=True) + if not frappe.flags.in_test: + frappe.local.session_obj.update(force=True) class Session: def __init__(self, user, resume=False, full_name=None, user_type=None): @@ -337,7 +335,7 @@ class Session: now = frappe.utils.now() self.data['data']['last_updated'] = now - self.data['data']['lang'] = text_type(frappe.lang) + self.data['data']['lang'] = str(frappe.lang) # update session in db last_updated = frappe.cache().hget("last_db_session_update", self.sid) diff --git a/frappe/share.py b/frappe/share.py index 63c6ce2f35..10c0f26c22 100644 --- a/frappe/share.py +++ b/frappe/share.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ from frappe.desk.form.document_follow import follow_document diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 2e2289aed4..aff3c7cd59 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ import json diff --git a/frappe/social/doctype/energy_point_log/test_energy_point_log.py b/frappe/social/doctype/energy_point_log/test_energy_point_log.py index 222c64389b..8c4dba5d6b 100644 --- a/frappe/social/doctype/energy_point_log/test_energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/test_energy_point_log.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2019, Frappe Technologies and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from .energy_point_log import get_energy_points as _get_energy_points, create_review_points_log, review diff --git a/frappe/social/doctype/energy_point_rule/energy_point_rule.py b/frappe/social/doctype/energy_point_rule/energy_point_rule.py index 9acb63d087..1c736528de 100644 --- a/frappe/social/doctype/energy_point_rule/energy_point_rule.py +++ b/frappe/social/doctype/energy_point_rule/energy_point_rule.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe import _ import frappe.cache_manager diff --git a/frappe/social/doctype/energy_point_settings/energy_point_settings.py b/frappe/social/doctype/energy_point_settings/energy_point_settings.py index 7299eef916..64944d64e8 100644 --- a/frappe/social/doctype/energy_point_settings/energy_point_settings.py +++ b/frappe/social/doctype/energy_point_settings/energy_point_settings.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.social.doctype.energy_point_log.energy_point_log import create_review_points_log diff --git a/frappe/social/doctype/post/post.py b/frappe/social/doctype/post/post.py index 9c7c1db1d4..62a0155d3c 100644 --- a/frappe/social/doctype/post/post.py +++ b/frappe/social/doctype/post/post.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe import requests from bs4 import BeautifulSoup diff --git a/frappe/social/doctype/post_comment/post_comment.py b/frappe/social/doctype/post_comment/post_comment.py index df24c4609a..14001ba537 100644 --- a/frappe/social/doctype/post_comment/post_comment.py +++ b/frappe/social/doctype/post_comment/post_comment.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.core.doctype.user.user import extract_mentions diff --git a/frappe/social/doctype/review_level/review_level.py b/frappe/social/doctype/review_level/review_level.py index 87720b63fc..6622a89ab9 100644 --- a/frappe/social/doctype/review_level/review_level.py +++ b/frappe/social/doctype/review_level/review_level.py @@ -2,7 +2,6 @@ # Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals # import frappe from frappe.model.document import Document diff --git a/frappe/templates/includes/comments/comments.html b/frappe/templates/includes/comments/comments.html index c490bedd72..935fa5367e 100644 --- a/frappe/templates/includes/comments/comments.html +++ b/frappe/templates/includes/comments/comments.html @@ -49,8 +49,10 @@ {% endif %} \ No newline at end of file diff --git a/frappe/templates/includes/feedback/feedback.py b/frappe/templates/includes/feedback/feedback.py new file mode 100644 index 0000000000..1830a3e09e --- /dev/null +++ b/frappe/templates/includes/feedback/feedback.py @@ -0,0 +1,63 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt +from __future__ import unicode_literals + +import frappe + +from frappe import _ + +@frappe.whitelist(allow_guest=True) +def add_feedback(reference_doctype, reference_name, rating, feedback, feedback_email): + doc = frappe.get_doc(reference_doctype, reference_name) + if doc.disable_feedback == 1: + return + + doc = frappe.new_doc('Feedback') + doc.reference_doctype = reference_doctype + doc.reference_name = reference_name + doc.rating = rating + doc.feedback = feedback + doc.email = feedback_email + doc.save(ignore_permissions=True) + + subject = _('New Feedback on {0}: {1}').format(reference_doctype, reference_name) + send_mail(doc, subject) + return doc + +@frappe.whitelist() +def update_feedback(reference_doctype, reference_name, rating, feedback, feedback_email): + doc = frappe.get_doc(reference_doctype, reference_name) + if doc.disable_feedback == 1: + return + + filters = { + "email": feedback_email, + "reference_doctype": reference_doctype, + "reference_name": reference_name + } + d = frappe.get_all('Feedback', filters=filters, limit=1) + doc = frappe.get_doc('Feedback', d[0].name) + doc.rating = rating + doc.feedback = feedback + doc.save(ignore_permissions=True) + + subject = _('Feedback updated on {0}: {1}').format(reference_doctype, reference_name) + send_mail(doc, subject) + return doc + +def send_mail(feedback, subject): + doc = frappe.get_doc(feedback.reference_doctype, feedback.reference_name) + + message = ("

    {0} ({1})

    ".format(feedback.feedback, feedback.rating) + + "

    {2}

    ".format(frappe.utils.get_request_site_address(), + feedback.name, + _("View Feedback"))) + + # notify creator + frappe.sendmail( + recipients=frappe.db.get_value('User', doc.owner, 'email') or doc.owner, + subject=subject, + message=message, + reference_doctype=doc.doctype, + reference_name=doc.name + ) diff --git a/frappe/templates/includes/full_index.html b/frappe/templates/includes/full_index.html index a7443c482a..eb8fb322f6 100644 --- a/frappe/templates/includes/full_index.html +++ b/frappe/templates/includes/full_index.html @@ -3,11 +3,6 @@ {% for item in children_map[route] %}
  • {{ item.title }} - {# - {% if children_map[item.route] %} - {{ make_item_list(item.route, children_map) }} - {% endif %} - #}
  • {% endfor %} diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index 52ac70513d..4400578862 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -291,15 +291,19 @@ var verify_token = function (event) { } var request_otp = function (r) { - $('.login-content').empty().append($('
    ').attr({ 'id': 'twofactor_div' }).html( - '
    \ -
    \ - {{ _("Verification") }}\ -
    \ -
    \ - \ - \ -
    ')); + $('.login-content').empty(); + $('.login-content:visible').append( + `
    +
    +
    + {{ _("Verification") }} +
    +
    + + +
    +
    ` + ); // add event handler for submit button verify_token(); } diff --git a/frappe/templates/pages/integrations/braintree_checkout.py b/frappe/templates/pages/integrations/braintree_checkout.py index ad262c34b2..c4aa1a7b9f 100644 --- a/frappe/templates/pages/integrations/braintree_checkout.py +++ b/frappe/templates/pages/integrations/braintree_checkout.py @@ -1,6 +1,5 @@ # Copyright (c) 2018, 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 diff --git a/frappe/templates/pages/integrations/payment_cancel.py b/frappe/templates/pages/integrations/payment_cancel.py index db335b8549..0387c9e50a 100644 --- a/frappe/templates/pages/integrations/payment_cancel.py +++ b/frappe/templates/pages/integrations/payment_cancel.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals import frappe def get_context(context): diff --git a/frappe/templates/pages/integrations/payment_success.py b/frappe/templates/pages/integrations/payment_success.py index bdc14db5b9..1eb9215121 100644 --- a/frappe/templates/pages/integrations/payment_success.py +++ b/frappe/templates/pages/integrations/payment_success.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe no_cache = True diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py index bc385b5784..1bd9457ef6 100644 --- a/frappe/templates/pages/integrations/paytm_checkout.py +++ b/frappe/templates/pages/integrations/paytm_checkout.py @@ -1,6 +1,5 @@ # 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 _ import json diff --git a/frappe/templates/pages/integrations/razorpay_checkout.py b/frappe/templates/pages/integrations/razorpay_checkout.py index 039ea1776e..1901577d4b 100644 --- a/frappe/templates/pages/integrations/razorpay_checkout.py +++ b/frappe/templates/pages/integrations/razorpay_checkout.py @@ -1,11 +1,9 @@ # 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 no_cache = 1 @@ -47,7 +45,7 @@ def get_api_key(): def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname, token): data = {} - if isinstance(options, string_types): + if isinstance(options, str): data = json.loads(options) data.update({ diff --git a/frappe/templates/pages/integrations/stripe_checkout.py b/frappe/templates/pages/integrations/stripe_checkout.py index 142d5b35cd..186c3e0942 100644 --- a/frappe/templates/pages/integrations/stripe_checkout.py +++ b/frappe/templates/pages/integrations/stripe_checkout.py @@ -1,6 +1,5 @@ # 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 cint, fmt_money diff --git a/frappe/templates/test/_test_base.html b/frappe/templates/test/_test_base.html index a0b1a83c97..1d5019df37 100644 --- a/frappe/templates/test/_test_base.html +++ b/frappe/templates/test/_test_base.html @@ -1,6 +1,7 @@ - + +

    This is for testing

    {% block content %}{% endblock %} diff --git a/frappe/templates/test/_test_base_breadcrumbs.html b/frappe/templates/test/_test_base_breadcrumbs.html new file mode 100644 index 0000000000..17caf8df1b --- /dev/null +++ b/frappe/templates/test/_test_base_breadcrumbs.html @@ -0,0 +1,20 @@ + + + + {%- block style %} + {% if colocated_css -%} + + {%- endif %} + {%- endblock -%} + + + {% include "templates/includes/breadcrumbs.html" %} +

    This is for testing

    + {% block content %}{% endblock %} + {%- block script %} + {% if colocated_js -%} + + {%- endif %} + {%- endblock %} + + diff --git a/frappe/test_runner.py b/frappe/test_runner.py index cd71dd46c5..0c30fbbd00 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function - import frappe import unittest, json, sys, os import time @@ -11,8 +8,8 @@ import importlib from frappe.modules import load_doctype_module, get_module_name import frappe.utils.scheduler import cProfile, pstats -from six import StringIO -from six.moves import reload_module +from io import StringIO +from importlib import reload from frappe.model.naming import revert_series_if_last unittest_runner = unittest.TextTestRunner @@ -178,6 +175,7 @@ def run_tests_for_module(module, verbose=False, tests=(), profile=False, junit_x for doctype in module.test_dependencies: make_test_records(doctype, verbose=verbose) + frappe.db.commit() return _run_unittest(module, verbose=verbose, tests=tests, profile=profile, junit_xml_output=junit_xml_output) def _run_unittest(modules, verbose=False, tests=(), profile=False, junit_xml_output=False): @@ -281,7 +279,7 @@ def get_modules(doctype): try: test_module = load_doctype_module(doctype, module, "test_") if test_module: - reload_module(test_module) + reload(test_module) except ImportError: test_module = None diff --git a/frappe/tests/test_api.py b/frappe/tests/test_api.py index 6453062877..32a5ebbd72 100644 --- a/frappe/tests/test_api.py +++ b/frappe/tests/test_api.py @@ -1,178 +1,176 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt -from __future__ import unicode_literals - -import unittest, frappe, os -from frappe.core.doctype.user.user import generate_keys -from frappe.frappeclient import FrappeClient, FrappeException -from frappe.utils.data import get_url +import unittest +from random import choice import requests -import base64 +from semantic_version import Version -class TestAPI(unittest.TestCase): - def test_insert_many(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabNote` where title in ('Sing','a','song','of','sixpence')") +import frappe +from frappe.utils import get_site_url + + +def maintain_state(f): + def wrapper(*args, **kwargs): + frappe.db.rollback() + r = f(*args, **kwargs) frappe.db.commit() + return r - server.insert_many([ - {"doctype": "Note", "public": True, "title": "Sing"}, - {"doctype": "Note", "public": True, "title": "a"}, - {"doctype": "Note", "public": True, "title": "song"}, - {"doctype": "Note", "public": True, "title": "of"}, - {"doctype": "Note", "public": True, "title": "sixpence"}, - ]) + return wrapper - self.assertTrue(frappe.db.get_value('Note', {'title': 'Sing'})) - self.assertTrue(frappe.db.get_value('Note', {'title': 'a'})) - self.assertTrue(frappe.db.get_value('Note', {'title': 'song'})) - self.assertTrue(frappe.db.get_value('Note', {'title': 'of'})) - self.assertTrue(frappe.db.get_value('Note', {'title': 'sixpence'})) - def test_create_doc(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabNote` where title = 'test_create'") - frappe.db.commit() +class TestResourceAPI(unittest.TestCase): + SITE_URL = get_site_url(frappe.local.site) + RESOURCE_URL = f"{SITE_URL}/api/resource" + DOCTYPE = "ToDo" + GENERATED_DOCUMENTS = [] - server.insert({"doctype": "Note", "public": True, "title": "test_create"}) + @classmethod + @maintain_state + def setUpClass(self): + for _ in range(10): + doc = frappe.get_doc( + {"doctype": "ToDo", "description": frappe.mock("paragraph")} + ).insert() + self.GENERATED_DOCUMENTS.append(doc.name) - self.assertTrue(frappe.db.get_value('Note', {'title': 'test_create'})) + @classmethod + @maintain_state + def tearDownClass(self): + for name in self.GENERATED_DOCUMENTS: + frappe.delete_doc_if_exists(self.DOCTYPE, name) - def test_list_docs(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - doc_list = server.get_list("Note") + def setUp(self): + # commit to ensure consistency in session (postgres CI randomly fails) + if frappe.conf.db_type == "postgres": + frappe.db.commit() - self.assertTrue(len(doc_list)) + @property + def sid(self): + if not getattr(self, "_sid", None): + self._sid = requests.post( + f"{self.SITE_URL}/api/method/login", + data={ + "usr": "Administrator", + "pwd": frappe.conf.admin_password or "admin", + }, + ).cookies.get("sid") - def test_get_doc(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabNote` where title = 'get_this'") - frappe.db.commit() + return self._sid - server.insert_many([ - {"doctype": "Note", "public": True, "title": "get_this"}, - ]) - doc = server.get_doc("Note", "get_this") - self.assertTrue(doc) + def get(self, path, params=""): + return requests.get(f"{self.RESOURCE_URL}/{path}?sid={self.sid}{params}") - def test_get_value(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabNote` where title = 'get_value'") - frappe.db.commit() - - test_content = "test get value" - - server.insert_many([ - {"doctype": "Note", "public": True, "title": "get_value", "content": test_content}, - ]) - self.assertEqual(server.get_value("Note", "content", {"title": "get_value"}).get('content'), test_content) - name = server.get_value("Note", "name", {"title": "get_value"}).get('name') - - # test by name - self.assertEqual(server.get_value("Note", "content", name).get('content'), test_content) - - self.assertRaises(FrappeException, server.get_value, "Note", "(select (password) from(__Auth) order by name desc limit 1)", {"title": "get_value"}) - - def test_get_single(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - server.set_value('Website Settings', 'Website Settings', 'title_prefix', 'test-prefix') - self.assertEqual(server.get_value('Website Settings', 'title_prefix', 'Website Settings').get('title_prefix'), 'test-prefix') - self.assertEqual(server.get_value('Website Settings', 'title_prefix').get('title_prefix'), 'test-prefix') - frappe.db.set_value('Website Settings', None, 'title_prefix', '') - - def test_update_doc(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabNote` where title in ('Sing','sing')") - frappe.db.commit() - - server.insert({"doctype":"Note", "public": True, "title": "Sing"}) - doc = server.get_doc("Note", 'Sing') - changed_title = "sing" - doc["title"] = changed_title - doc = server.update(doc) - self.assertTrue(doc["title"] == changed_title) - - def test_update_child_doc(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabContact` where first_name = 'George' and last_name = 'Steevens'") - frappe.db.sql("delete from `tabContact` where first_name = 'William' and last_name = 'Shakespeare'") - frappe.db.sql("delete from `tabCommunication` where reference_doctype = 'Event'") - frappe.db.sql("delete from `tabCommunication Link` where link_doctype = 'Contact'") - frappe.db.sql("delete from `tabEvent` where subject = 'Sing a song of sixpence'") - frappe.db.sql("delete from `tabEvent Participants` where reference_doctype = 'Contact'") - frappe.db.commit() - - # create multiple contacts - server.insert_many([ - {"doctype": "Contact", "first_name": "George", "last_name": "Steevens"}, - {"doctype": "Contact", "first_name": "William", "last_name": "Shakespeare"} - ]) - - # create an event with one of the created contacts - event = server.insert({ - "doctype": "Event", - "subject": "Sing a song of sixpence", - "event_participants": [{ - "reference_doctype": "Contact", - "reference_docname": "George Steevens" - }] - }) - - # update the event's contact to the second contact - server.update({ - "doctype": "Event Participants", - "name": event.get("event_participants")[0].get("name"), - "reference_docname": "William Shakespeare" - }) - - # the change should run the parent document's validations and - # create a Communication record with the new contact - self.assertTrue(frappe.db.exists("Communication Link", {"link_name": "William Shakespeare"})) - - def test_delete_doc(self): - server = FrappeClient(get_url(), "Administrator", "admin", verify=False) - frappe.db.sql("delete from `tabNote` where title = 'delete'") - frappe.db.commit() - - server.insert_many([ - {"doctype": "Note", "public": True, "title": "delete"}, - ]) - server.delete("Note", "delete") - - self.assertFalse(frappe.db.get_value('Note', {'title': 'delete'})) - - def test_auth_via_api_key_secret(self): - # generate API key and API secret for administrator - keys = generate_keys("Administrator") - frappe.db.commit() - generated_secret = frappe.utils.password.get_decrypted_password( - "User", "Administrator", fieldname='api_secret' + def post(self, path, data): + return requests.post( + f"{self.RESOURCE_URL}/{path}?sid={self.sid}", data=frappe.as_json(data) ) - api_key = frappe.db.get_value("User", "Administrator", "api_key") - header = {"Authorization": "token {}:{}".format(api_key, generated_secret)} - res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) + def put(self, path, data): + return requests.put( + f"{self.RESOURCE_URL}/{path}?sid={self.sid}", data=frappe.as_json(data) + ) - self.assertEqual(res.status_code, 200) - self.assertEqual("Administrator", res.json()["message"]) - self.assertEqual(keys['api_secret'], generated_secret) + def delete(self, path): + return requests.delete(f"{self.RESOURCE_URL}/{path}?sid={self.sid}") - header = {"Authorization": "Basic {}".format(base64.b64encode(frappe.safe_encode("{}:{}".format(api_key, generated_secret))).decode())} - res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) - self.assertEqual(res.status_code, 200) - self.assertEqual("Administrator", res.json()["message"]) + def test_unauthorized_call(self): + # test 1: fetch documents without auth + response = requests.get(f"{self.RESOURCE_URL}/{self.DOCTYPE}") + self.assertEqual(response.status_code, 403) - # Valid api key, invalid api secret - api_secret = "ksk&93nxoe3os" - header = {"Authorization": "token {}:{}".format(api_key, api_secret)} - res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) - self.assertEqual(res.status_code, 403) + def test_get_list(self): + # test 2: fetch documents without params + response = self.get(self.DOCTYPE) + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.json(), dict) + self.assertIn("data", response.json()) + + def test_get_list_limit(self): + # test 3: fetch data with limit + response = self.get(self.DOCTYPE, "&limit=2") + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.json()["data"]), 2) + + def test_get_list_dict(self): + # test 4: fetch response as (not) dict + response = self.get(self.DOCTYPE, "&as_dict=True") + json = frappe._dict(response.json()) + self.assertEqual(response.status_code, 200) + self.assertIsInstance(json.data, list) + self.assertIsInstance(json.data[0], dict) + + response = self.get(self.DOCTYPE, "&as_dict=False") + json = frappe._dict(response.json()) + self.assertEqual(response.status_code, 200) + self.assertIsInstance(json.data, list) + self.assertIsInstance(json.data[0], list) + + def test_get_list_debug(self): + # test 5: fetch response with debug + response = self.get(self.DOCTYPE, "&debug=true") + self.assertEqual(response.status_code, 200) + self.assertIn("exc", response.json()) + self.assertIsInstance(response.json()["exc"], str) + self.assertIsInstance(eval(response.json()["exc"]), list) + + def test_get_list_fields(self): + # test 6: fetch response with fields + response = self.get(self.DOCTYPE, r'&fields=["description"]') + self.assertEqual(response.status_code, 200) + json = frappe._dict(response.json()) + self.assertIn("description", json.data[0]) + + def test_create_document(self): + # test 7: POST method on /api/resource to create doc + data = {"description": frappe.mock("paragraph")} + response = self.post(self.DOCTYPE, data) + self.assertEqual(response.status_code, 200) + docname = response.json()["data"]["name"] + self.assertIsInstance(docname, str) + self.GENERATED_DOCUMENTS.append(docname) + + def test_update_document(self): + # test 8: PUT method on /api/resource to update doc + generated_desc = frappe.mock("paragraph") + data = {"description": generated_desc} + random_doc = choice(self.GENERATED_DOCUMENTS) + desc_before_update = frappe.db.get_value(self.DOCTYPE, random_doc, "description") + + response = self.put(f"{self.DOCTYPE}/{random_doc}", data=data) + self.assertEqual(response.status_code, 200) + self.assertNotEqual(response.json()["data"]["description"], desc_before_update) + self.assertEqual(response.json()["data"]["description"], generated_desc) + + def test_delete_document(self): + # test 9: DELETE method on /api/resource + doc_to_delete = choice(self.GENERATED_DOCUMENTS) + response = self.delete(f"{self.DOCTYPE}/{doc_to_delete}") + self.assertEqual(response.status_code, 202) + self.assertDictEqual(response.json(), {"message": "ok"}) + self.GENERATED_DOCUMENTS.remove(doc_to_delete) + + non_existent_doc = frappe.generate_hash(length=12) + response = self.delete(f"{self.DOCTYPE}/{non_existent_doc}") + self.assertEqual(response.status_code, 404) + self.assertDictEqual(response.json(), {}) - # random api key and api secret - api_key = "@3djdk3kld" - api_secret = "ksk&93nxoe3os" - header = {"Authorization": "token {}:{}".format(api_key, api_secret)} - res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) - self.assertEqual(res.status_code, 401) +class TestMethodAPI(unittest.TestCase): + METHOD_URL = f"{get_site_url(frappe.local.site)}/api/method" + + def test_version(self): + # test 1: test for /api/method/version + response = requests.get(f"{self.METHOD_URL}/version") + json = frappe._dict(response.json()) + + self.assertEqual(response.status_code, 200) + self.assertIsInstance(json, dict) + self.assertIsInstance(json.message, str) + self.assertEqual(Version(json.message), Version(frappe.__version__)) + + def test_ping(self): + # test 2: test for /api/method/ping + response = requests.get(f"{self.METHOD_URL}/ping") + self.assertEqual(response.status_code, 200) + self.assertIsInstance(response.json(), dict) + self.assertEqual(response.json()['message'], "pong") diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index 439e1546c0..e9c1ccec6d 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest import frappe.desk.form.assign_to from frappe.desk.listview import get_group_by_count diff --git a/frappe/tests/test_auth.py b/frappe/tests/test_auth.py index bbe9c36aea..bc23cb591c 100644 --- a/frappe/tests/test_auth.py +++ b/frappe/tests/test_auth.py @@ -1,7 +1,5 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import time import unittest diff --git a/frappe/tests/test_bot.py b/frappe/tests/test_bot.py index b098584a8f..4500ab95ac 100644 --- a/frappe/tests/test_bot.py +++ b/frappe/tests/test_bot.py @@ -3,7 +3,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest class TestBot(unittest.TestCase): diff --git a/frappe/tests/test_client.py b/frappe/tests/test_client.py index 6be437601b..aed8dc8581 100644 --- a/frappe/tests/test_client.py +++ b/frappe/tests/test_client.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -from __future__ import unicode_literals - import unittest import frappe diff --git a/frappe/tests/test_commands.py b/frappe/tests/test_commands.py index b6cd0b575c..07bdf8791e 100644 --- a/frappe/tests/test_commands.py +++ b/frappe/tests/test_commands.py @@ -80,7 +80,7 @@ def exists_in_backup(doctypes, file): ) with gzip.open(file, "rb") as f: content = f.read().decode("utf8") - return all([predicate.format(doctype).lower() in content.lower() for doctype in doctypes]) + return all(predicate.format(doctype).lower() in content.lower() for doctype in doctypes) class BaseTestCommands(unittest.TestCase): @@ -355,12 +355,12 @@ class TestCommands(BaseTestCommands): # test 2: bare functionality for single site self.execute("bench --site {site} list-apps") self.assertEqual(self.returncode, 0) - list_apps = set([ + list_apps = set( _x.split()[0] for _x in self.stdout.split("\n") - ]) + ) doctype = frappe.get_single("Installed Applications").installed_applications if doctype: - installed_apps = set([x.app_name for x in doctype]) + installed_apps = set(x.app_name for x in doctype) else: installed_apps = set(frappe.get_installed_apps()) self.assertSetEqual(list_apps, installed_apps) diff --git a/frappe/tests/test_cors.py b/frappe/tests/test_cors.py index d4ed260f61..38c3fd8132 100644 --- a/frappe/tests/test_cors.py +++ b/frappe/tests/test_cors.py @@ -1,7 +1,5 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest from werkzeug.wrappers import Response from frappe.app import process_response diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 79ab8bf421..a31a898d73 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -3,8 +3,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest from random import choice diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 085c257550..89975b46d6 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest from frappe.model.db_query import DatabaseQuery @@ -23,6 +21,18 @@ class TestReportview(unittest.TestCase): def test_basic(self): self.assertTrue({"name":"DocType"} in DatabaseQuery("DocType").execute(limit_page_length=None)) + def test_extract_tables(self): + db_query = DatabaseQuery("DocType") + add_custom_field("DocType", 'test_tab_field', 'Data') + + db_query.fields = ["tabNote.creation", "test_tab_field", "tabDocType.test_tab_field"] + db_query.extract_tables() + self.assertIn("`tabNote`", db_query.tables) + self.assertIn("`tabDocType`", db_query.tables) + self.assertNotIn("test_tab_field", db_query.tables) + + clear_custom_fields("DocType") + def test_build_match_conditions(self): clear_user_permissions_for_doctype('Blog Post', 'test2@example.com') diff --git a/frappe/tests/test_defaults.py b/frappe/tests/test_defaults.py index 210ea0d36c..d051c0a184 100644 --- a/frappe/tests/test_defaults.py +++ b/frappe/tests/test_defaults.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest from frappe.defaults import * diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py index 1a5a8721fd..7c0c95671a 100644 --- a/frappe/tests/test_document.py +++ b/frappe/tests/test_document.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import os import unittest diff --git a/frappe/tests/test_document_locks.py b/frappe/tests/test_document_locks.py index c270b1bf28..650a913bee 100644 --- a/frappe/tests/test_document_locks.py +++ b/frappe/tests/test_document_locks.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest class TestDocumentLocks(unittest.TestCase): diff --git a/frappe/tests/test_domainification.py b/frappe/tests/test_domainification.py index 2fd2e7f3ff..c9acd9ec45 100644 --- a/frappe/tests/test_domainification.py +++ b/frappe/tests/test_domainification.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest, frappe from frappe.core.page.permission_manager.permission_manager import get_roles_and_doctypes from frappe.desk.doctype.desktop_icon.desktop_icon import (get_desktop_icons, add_user_icon, diff --git a/frappe/tests/test_dynamic_links.py b/frappe/tests/test_dynamic_links.py index 98da2cbc35..04ccc91ff2 100644 --- a/frappe/tests/test_dynamic_links.py +++ b/frappe/tests/test_dynamic_links.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest class TestDynamicLinks(unittest.TestCase): diff --git a/frappe/tests/test_email.py b/frappe/tests/test_email.py index 8340db4852..a837a2c572 100644 --- a/frappe/tests/test_email.py +++ b/frappe/tests/test_email.py @@ -1,10 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest, frappe, re, email -from six import PY3 test_dependencies = ['Email Account'] @@ -118,10 +115,7 @@ class TestEmail(unittest.TestCase): content = part.get_payload(decode=True) if content: - if PY3: - eol = "\r\n" - else: - eol = "\n" + eol = "\r\n" frappe.local.flags.signed_query_string = \ re.search(r'(?<=/api/method/frappe.email.queue.unsubscribe\?).*(?=' + eol + ')', @@ -145,7 +139,8 @@ class TestEmail(unittest.TestCase): self.assertEqual(len(queue_recipients), 2) def test_unsubscribe(self): - from frappe.email.queue import unsubscribe, send + from frappe.email.queue import unsubscribe + from frappe.email.doctype.email_queue.email_queue import QueueBuilder unsubscribe(doctype="User", name="Administrator", email="test@example.com") self.assertTrue(frappe.db.get_value("Email Unsubscribe", @@ -154,11 +149,11 @@ class TestEmail(unittest.TestCase): before = frappe.db.sql("""select count(name) from `tabEmail Queue` where status='Not Sent'""")[0][0] - send(recipients=['test@example.com', 'test1@example.com'], - sender="admin@example.com", - reference_doctype='User', reference_name="Administrator", - subject='Testing Email Queue', message='This is mail is queued!', unsubscribe_message="Unsubscribe") - + builder = QueueBuilder(recipients=['test@example.com', 'test1@example.com'], + sender="admin@example.com", + reference_doctype='User', reference_name="Administrator", + subject='Testing Email Queue', message='This is mail is queued!', unsubscribe_message="Unsubscribe") + builder.process() # this is sent async (?) email_queue = frappe.db.sql("""select name from `tabEmail Queue` where status='Not Sent'""", @@ -178,7 +173,8 @@ class TestEmail(unittest.TestCase): frappe.db.sql('''delete from `tabCommunication` where sender = 'sukh@yyy.com' ''') with open(frappe.get_app_path('frappe', 'tests', 'data', 'email_with_image.txt'), 'r') as raw: - communication = email_account.insert_communication(raw.read()) + mails = email_account.get_inbound_mails(test_mails=[raw.read()]) + communication = mails[0].process() self.assertTrue(re.search(''']*src=["']/private/files/rtco1.png[^>]*>''', communication.content)) self.assertTrue(re.search(''']*src=["']/private/files/rtco2.png[^>]*>''', communication.content)) diff --git a/frappe/tests/test_exporter_fixtures.py b/frappe/tests/test_exporter_fixtures.py index b8bd94e3e9..0893500daf 100644 --- a/frappe/tests/test_exporter_fixtures.py +++ b/frappe/tests/test_exporter_fixtures.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe import frappe.defaults from frappe.core.doctype.data_import.data_import import export_csv diff --git a/frappe/tests/test_fmt_datetime.py b/frappe/tests/test_fmt_datetime.py index e19eb25fe6..8f5408948d 100644 --- a/frappe/tests/test_fmt_datetime.py +++ b/frappe/tests/test_fmt_datetime.py @@ -1,7 +1,5 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import datetime import frappe diff --git a/frappe/tests/test_fmt_money.py b/frappe/tests/test_fmt_money.py index a1321658b7..160ea33378 100644 --- a/frappe/tests/test_fmt_money.py +++ b/frappe/tests/test_fmt_money.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe from frappe.utils import fmt_money import unittest diff --git a/frappe/tests/test_form_load.py b/frappe/tests/test_form_load.py index b382fa7381..dd6ccd9bcd 100644 --- a/frappe/tests/test_form_load.py +++ b/frappe/tests/test_form_load.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest from frappe.desk.form.load import getdoctype, getdoc from frappe.core.page.permission_manager.permission_manager import update, reset, add diff --git a/frappe/tests/test_frappe_client.py b/frappe/tests/test_frappe_client.py new file mode 100644 index 0000000000..e1cdbb6ccd --- /dev/null +++ b/frappe/tests/test_frappe_client.py @@ -0,0 +1,177 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import unittest, frappe +from frappe.core.doctype.user.user import generate_keys +from frappe.frappeclient import FrappeClient, FrappeException +from frappe.utils.data import get_url + +import requests +import base64 + +class TestFrappeClient(unittest.TestCase): + def test_insert_many(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabNote` where title in ('Sing','a','song','of','sixpence')") + frappe.db.commit() + + server.insert_many([ + {"doctype": "Note", "public": True, "title": "Sing"}, + {"doctype": "Note", "public": True, "title": "a"}, + {"doctype": "Note", "public": True, "title": "song"}, + {"doctype": "Note", "public": True, "title": "of"}, + {"doctype": "Note", "public": True, "title": "sixpence"}, + ]) + + self.assertTrue(frappe.db.get_value('Note', {'title': 'Sing'})) + self.assertTrue(frappe.db.get_value('Note', {'title': 'a'})) + self.assertTrue(frappe.db.get_value('Note', {'title': 'song'})) + self.assertTrue(frappe.db.get_value('Note', {'title': 'of'})) + self.assertTrue(frappe.db.get_value('Note', {'title': 'sixpence'})) + + def test_create_doc(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabNote` where title = 'test_create'") + frappe.db.commit() + + server.insert({"doctype": "Note", "public": True, "title": "test_create"}) + + self.assertTrue(frappe.db.get_value('Note', {'title': 'test_create'})) + + def test_list_docs(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + doc_list = server.get_list("Note") + + self.assertTrue(len(doc_list)) + + def test_get_doc(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabNote` where title = 'get_this'") + frappe.db.commit() + + server.insert_many([ + {"doctype": "Note", "public": True, "title": "get_this"}, + ]) + doc = server.get_doc("Note", "get_this") + self.assertTrue(doc) + + def test_get_value(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabNote` where title = 'get_value'") + frappe.db.commit() + + test_content = "test get value" + + server.insert_many([ + {"doctype": "Note", "public": True, "title": "get_value", "content": test_content}, + ]) + self.assertEqual(server.get_value("Note", "content", {"title": "get_value"}).get('content'), test_content) + name = server.get_value("Note", "name", {"title": "get_value"}).get('name') + + # test by name + self.assertEqual(server.get_value("Note", "content", name).get('content'), test_content) + + self.assertRaises(FrappeException, server.get_value, "Note", "(select (password) from(__Auth) order by name desc limit 1)", {"title": "get_value"}) + + def test_get_single(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + server.set_value('Website Settings', 'Website Settings', 'title_prefix', 'test-prefix') + self.assertEqual(server.get_value('Website Settings', 'title_prefix', 'Website Settings').get('title_prefix'), 'test-prefix') + self.assertEqual(server.get_value('Website Settings', 'title_prefix').get('title_prefix'), 'test-prefix') + frappe.db.set_value('Website Settings', None, 'title_prefix', '') + + def test_update_doc(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabNote` where title in ('Sing','sing')") + frappe.db.commit() + + server.insert({"doctype":"Note", "public": True, "title": "Sing"}) + doc = server.get_doc("Note", 'Sing') + changed_title = "sing" + doc["title"] = changed_title + doc = server.update(doc) + self.assertTrue(doc["title"] == changed_title) + + def test_update_child_doc(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabContact` where first_name = 'George' and last_name = 'Steevens'") + frappe.db.sql("delete from `tabContact` where first_name = 'William' and last_name = 'Shakespeare'") + frappe.db.sql("delete from `tabCommunication` where reference_doctype = 'Event'") + frappe.db.sql("delete from `tabCommunication Link` where link_doctype = 'Contact'") + frappe.db.sql("delete from `tabEvent` where subject = 'Sing a song of sixpence'") + frappe.db.sql("delete from `tabEvent Participants` where reference_doctype = 'Contact'") + frappe.db.commit() + + # create multiple contacts + server.insert_many([ + {"doctype": "Contact", "first_name": "George", "last_name": "Steevens"}, + {"doctype": "Contact", "first_name": "William", "last_name": "Shakespeare"} + ]) + + # create an event with one of the created contacts + event = server.insert({ + "doctype": "Event", + "subject": "Sing a song of sixpence", + "event_participants": [{ + "reference_doctype": "Contact", + "reference_docname": "George Steevens" + }] + }) + + # update the event's contact to the second contact + server.update({ + "doctype": "Event Participants", + "name": event.get("event_participants")[0].get("name"), + "reference_docname": "William Shakespeare" + }) + + # the change should run the parent document's validations and + # create a Communication record with the new contact + self.assertTrue(frappe.db.exists("Communication Link", {"link_name": "William Shakespeare"})) + + def test_delete_doc(self): + server = FrappeClient(get_url(), "Administrator", "admin", verify=False) + frappe.db.sql("delete from `tabNote` where title = 'delete'") + frappe.db.commit() + + server.insert_many([ + {"doctype": "Note", "public": True, "title": "delete"}, + ]) + server.delete("Note", "delete") + + self.assertFalse(frappe.db.get_value('Note', {'title': 'delete'})) + + def test_auth_via_api_key_secret(self): + # generate API key and API secret for administrator + keys = generate_keys("Administrator") + frappe.db.commit() + generated_secret = frappe.utils.password.get_decrypted_password( + "User", "Administrator", fieldname='api_secret' + ) + + api_key = frappe.db.get_value("User", "Administrator", "api_key") + header = {"Authorization": "token {}:{}".format(api_key, generated_secret)} + res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) + + self.assertEqual(res.status_code, 200) + self.assertEqual("Administrator", res.json()["message"]) + self.assertEqual(keys['api_secret'], generated_secret) + + header = {"Authorization": "Basic {}".format(base64.b64encode(frappe.safe_encode("{}:{}".format(api_key, generated_secret))).decode())} + res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) + self.assertEqual(res.status_code, 200) + self.assertEqual("Administrator", res.json()["message"]) + + # Valid api key, invalid api secret + api_secret = "ksk&93nxoe3os" + header = {"Authorization": "token {}:{}".format(api_key, api_secret)} + res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) + self.assertEqual(res.status_code, 403) + + + # random api key and api secret + api_key = "@3djdk3kld" + api_secret = "ksk&93nxoe3os" + header = {"Authorization": "token {}:{}".format(api_key, api_secret)} + res = requests.post(get_url() + "/api/method/frappe.auth.get_logged_user", headers=header) + self.assertEqual(res.status_code, 401) diff --git a/frappe/tests/test_geo_ip.py b/frappe/tests/test_geo_ip.py index b292e43ba5..f085838f37 100644 --- a/frappe/tests/test_geo_ip.py +++ b/frappe/tests/test_geo_ip.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest class TestGeoIP(unittest.TestCase): diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index 5cffbaaf64..3921af6738 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe diff --git a/frappe/tests/test_goal.py b/frappe/tests/test_goal.py index b4c0736482..5a83baa1af 100644 --- a/frappe/tests/test_goal.py +++ b/frappe/tests/test_goal.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe diff --git a/frappe/tests/test_hooks.py b/frappe/tests/test_hooks.py index ff71e2414c..3f66afe34a 100644 --- a/frappe/tests/test_hooks.py +++ b/frappe/tests/test_hooks.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe from frappe.desk.doctype.todo.todo import ToDo @@ -19,12 +18,12 @@ class TestHooks(unittest.TestCase): def test_override_doctype_class(self): from frappe import hooks - + # Set hook hooks.override_doctype_class = { 'ToDo': ['frappe.tests.test_hooks.CustomToDo'] } - + # Clear cache frappe.cache().delete_value('app_hooks') clear_controller_cache('ToDo') diff --git a/frappe/tests/test_listview.py b/frappe/tests/test_listview.py index 1ef72fdd32..4efb570e7e 100644 --- a/frappe/tests/test_listview.py +++ b/frappe/tests/test_listview.py @@ -1,7 +1,5 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest import frappe import json diff --git a/frappe/tests/test_monitor.py b/frappe/tests/test_monitor.py index b447e89b06..dbc5fe35e1 100644 --- a/frappe/tests/test_monitor.py +++ b/frappe/tests/test_monitor.py @@ -2,7 +2,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe import frappe.monitor diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index 66d48e3612..557993882f 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe from frappe.utils import now_datetime diff --git a/frappe/tests/test_oauth20.py b/frappe/tests/test_oauth20.py index cf3b2ea90d..2a5bed34b3 100644 --- a/frappe/tests/test_oauth20.py +++ b/frappe/tests/test_oauth20.py @@ -302,7 +302,7 @@ class TestOAuth20(unittest.TestCase): id_token, audience=client.client_id, key=client.client_secret, - algorithm="HS256", + algorithms=["HS256"], ) self.assertTrue(payload.get("nonce") == nonce) diff --git a/frappe/tests/test_password.py b/frappe/tests/test_password.py index 98141072e2..731841737c 100644 --- a/frappe/tests/test_password.py +++ b/frappe/tests/test_password.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import unittest -from frappe.utils.password import update_password, check_password, passlibctx - +from frappe.utils.password import update_password, check_password, passlibctx, encrypt, decrypt +from cryptography.fernet import Fernet class TestPassword(unittest.TestCase): def setUp(self): frappe.delete_doc('Email Account', 'Test Email Account Password') @@ -105,6 +104,16 @@ class TestPassword(unittest.TestCase): doc.save() self.assertEqual(doc.get_password(raise_exception=False), None) + def test_custom_encryption_key(self): + text = 'Frappe Framework' + custom_encryption_key = Fernet.generate_key().decode() + + encrypted_text = encrypt(text, encryption_key=custom_encryption_key) + decrypted_text = decrypt(encrypted_text, encryption_key=custom_encryption_key) + + self.assertEqual(text, decrypted_text) + + pass def get_password_list(doc): return frappe.db.sql("""SELECT `password` diff --git a/frappe/tests/test_patches.py b/frappe/tests/test_patches.py index ed22afc0af..7f4efc700c 100644 --- a/frappe/tests/test_patches.py +++ b/frappe/tests/test_patches.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import unittest, frappe from frappe.modules import patch_handler diff --git a/frappe/tests/test_pdf.py b/frappe/tests/test_pdf.py index be287e0d92..9e22b9ddbf 100644 --- a/frappe/tests/test_pdf.py +++ b/frappe/tests/test_pdf.py @@ -1,14 +1,14 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - +import io import unittest -import frappe.utils.pdf as pdfgen -import frappe, io, six from PyPDF2 import PdfFileReader -#class TestPdfBorders(unittest.TestCase): +import frappe +import frappe.utils.pdf as pdfgen + + class TestPdf(unittest.TestCase): @property def html(self): @@ -44,8 +44,6 @@ class TestPdf(unittest.TestCase): pdf = pdfgen.get_pdf(self.html, options={"password": password}) reader = PdfFileReader(io.BytesIO(pdf)) self.assertTrue(reader.isEncrypted) - if six.PY2: - password = frappe.safe_encode(password) self.assertTrue(reader.decrypt(password)) def test_pdf_generation_as_a_user(self): diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py index 82f0ee920a..ada64156de 100644 --- a/frappe/tests/test_permissions.py +++ b/frappe/tests/test_permissions.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - """Use blog post test to test user permissions logic""" import frappe diff --git a/frappe/tests/test_query_report.py b/frappe/tests/test_query_report.py index eaf4dc7070..3d9791d399 100644 --- a/frappe/tests/test_query_report.py +++ b/frappe/tests/test_query_report.py @@ -1,8 +1,6 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest import frappe diff --git a/frappe/tests/test_rate_limiter.py b/frappe/tests/test_rate_limiter.py index ae1857bb31..71977935a9 100644 --- a/frappe/tests/test_rate_limiter.py +++ b/frappe/tests/test_rate_limiter.py @@ -3,8 +3,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest from werkzeug.wrappers import Response import time diff --git a/frappe/tests/test_recorder.py b/frappe/tests/test_recorder.py index 64d3c52f63..d9386ca25b 100644 --- a/frappe/tests/test_recorder.py +++ b/frappe/tests/test_recorder.py @@ -3,12 +3,11 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe import frappe.recorder from frappe.utils import set_request -from frappe.website.render import render_page +from frappe.website.serve import get_response_content import sqlparse @@ -122,5 +121,5 @@ class TestRecorder(unittest.TestCase): self.assertEqual(call['exact_copies'], query[1]) def test_error_page_rendering(self): - content = render_page("error") + content = get_response_content("error") self.assertIn("Error", content) diff --git a/frappe/tests/test_safe_exec.py b/frappe/tests/test_safe_exec.py index d7b25b8194..79f2c504a4 100644 --- a/frappe/tests/test_safe_exec.py +++ b/frappe/tests/test_safe_exec.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + import unittest, frappe from frappe.utils.safe_exec import safe_exec, get_safe_globals diff --git a/frappe/tests/test_scheduler.py b/frappe/tests/test_scheduler.py index d5344c60b5..ec43c08ab7 100644 --- a/frappe/tests/test_scheduler.py +++ b/frappe/tests/test_scheduler.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from unittest import TestCase from dateutil.relativedelta import relativedelta from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs diff --git a/frappe/tests/test_search.py b/frappe/tests/test_search.py index e39d6c4691..9ad02f49a6 100644 --- a/frappe/tests/test_search.py +++ b/frappe/tests/test_search.py @@ -1,7 +1,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import unittest import frappe from frappe.desk.search import search_link diff --git a/frappe/tests/test_seen.py b/frappe/tests/test_seen.py index 8eea30d773..7f4e8719a3 100644 --- a/frappe/tests/test_seen.py +++ b/frappe/tests/test_seen.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest, json class TestSeen(unittest.TestCase): diff --git a/frappe/tests/test_sitemap.py b/frappe/tests/test_sitemap.py index 22669000c1..e29a453a14 100644 --- a/frappe/tests/test_sitemap.py +++ b/frappe/tests/test_sitemap.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import frappe, unittest from frappe.utils import get_html_for_route diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index c29390c429..f51f31d509 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe, unittest, os import frappe.translate from frappe import _ diff --git a/frappe/tests/test_translation.py b/frappe/tests/test_translation.py deleted file mode 100644 index 0e1e6e452c..0000000000 --- a/frappe/tests/test_translation.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt -from __future__ import unicode_literals - -import frappe, unittest, os -import frappe.translate - -# class TestTranslations(unittest.TestCase): -# def test_doctype(self, messages=None): -# if not messages: -# messages = frappe.translate.get_messages_from_doctype("Role") -# self.assertTrue("Role Name" in messages) -# -# def test_page(self, messages=None): -# if not messages: -# messages = frappe.translate.get_messages_from_page("finder") -# self.assertTrue("Finder" in messages) -# -# def test_report(self, messages=None): -# if not messages: -# messages = frappe.translate.get_messages_from_report("ToDo") -# self.assertTrue("Test" in messages) -# -# def test_include_js(self, messages=None): -# if not messages: -# messages = frappe.translate.get_messages_from_include_files("frappe") -# self.assertTrue("History" in messages) -# -# def test_server(self, messages=None): -# if not messages: -# messages = frappe.translate.get_server_messages("frappe") -# self.assertTrue("Login" in messages) -# self.assertTrue("Did not save" in messages) -# -# def test_all_app(self): -# messages = frappe.translate.get_messages_for_app("frappe") -# self.test_doctype(messages) -# self.test_page(messages) -# self.test_report(messages) -# self.test_include_js(messages) -# self.test_server(messages) -# -# def test_load_translations(self): -# frappe.translate.clear_cache() -# self.assertFalse(frappe.cache().hget("lang_full_dict", "de")) -# -# langdict = frappe.translate.get_full_dict("de") -# self.assertEqual(langdict['Row'], 'Reihe') -# -# def test_write_csv(self): -# tpath = frappe.get_pymodule_path("frappe", "translations", "de.csv") -# if os.path.exists(tpath): -# os.remove(tpath) -# frappe.translate.write_translations_file("frappe", "de") -# self.assertTrue(os.path.exists(tpath)) -# self.assertEqual(dict(frappe.translate.read_csv_file(tpath)).get("Row"), "Reihe") -# -# def test_get_dict(self): -# frappe.local.lang = "de" -# self.assertEqual(frappe.get_lang_dict("doctype", "Role").get("Role"), "Rolle") -# frappe.local.lang = "en" -# -# if __name__=="__main__": -# frappe.connect("site1") -# unittest.main() diff --git a/frappe/tests/test_twofactor.py b/frappe/tests/test_twofactor.py index 709b88b8f3..7de155bc96 100644 --- a/frappe/tests/test_twofactor.py +++ b/frappe/tests/test_twofactor.py @@ -1,7 +1,5 @@ # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest, frappe, pyotp from frappe.auth import HTTPRequest from frappe.utils import cint diff --git a/frappe/tests/test_utils.py b/frappe/tests/test_utils.py index 74ceec8287..c2e5d99731 100644 --- a/frappe/tests/test_utils.py +++ b/frappe/tests/test_utils.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import unittest import frappe @@ -56,14 +54,14 @@ class TestMoney(unittest.TestCase): for num in nums_bhd: self.assertEqual( - money_in_words(num[0], "BHD"), - num[1], + money_in_words(num[0], "BHD"), + num[1], "{0} is not the same as {1}".format(money_in_words(num[0], "BHD"), num[1]) ) for num in nums_ngn: self.assertEqual( - money_in_words(num[0], "NGN"), num[1], + money_in_words(num[0], "NGN"), num[1], "{0} is not the same as {1}".format(money_in_words(num[0], "NGN"), num[1]) ) @@ -139,7 +137,7 @@ class TestValidationUtils(unittest.TestCase): # Valid URLs self.assertTrue(validate_url('https://google.com')) self.assertTrue(validate_url('http://frappe.io', throw=True)) - + # Invalid URLs without throw self.assertFalse(validate_url('google.io')) self.assertFalse(validate_url('google.io')) @@ -152,9 +150,9 @@ class TestValidationUtils(unittest.TestCase): self.assertTrue(validate_url('ftp://frappe.cloud', valid_schemes=['https', 'ftp'])) self.assertFalse(validate_url('bolo://frappe.io', valid_schemes=("http", "https", "ftp", "ftps"))) self.assertRaises( - frappe.ValidationError, - validate_url, - 'gopher://frappe.io', + frappe.ValidationError, + validate_url, + 'gopher://frappe.io', valid_schemes='https', throw=True ) @@ -167,16 +165,16 @@ class TestValidationUtils(unittest.TestCase): # Valid addresses self.assertTrue(validate_email_address('someone@frappe.com')) self.assertTrue(validate_email_address('someone@frappe.com, anyone@frappe.io')) - + # Invalid address self.assertFalse(validate_email_address('someone')) self.assertFalse(validate_email_address('someone@----.com')) # Invalid with throw self.assertRaises( - frappe.InvalidEmailAddressError, - validate_email_address, - 'someone.com', + frappe.InvalidEmailAddressError, + validate_email_address, + 'someone.com', throw=True ) diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index dc3862174d..ae768d7804 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -1,40 +1,41 @@ -from __future__ import unicode_literals - import unittest import frappe -from frappe.website import render -from frappe.website.utils import get_home_page from frappe.utils import set_request +from frappe.website.serve import get_response, get_response_content +from frappe.website.utils import (build_response, clear_website_cache, get_home_page) class TestWebsite(unittest.TestCase): + def setUp(self): + frappe.set_user('Guest') - def test_home_page_for_role(self): - frappe.delete_doc_if_exists('User', 'test-user-for-home-page@example.com') - frappe.delete_doc_if_exists('Role', 'home-page-test') - frappe.delete_doc_if_exists('Web Page', 'home-page-test') + def tearDown(self): + frappe.set_user('Administrator') + + def test_home_page(self): + frappe.set_user('Administrator') + # test home page via role user = frappe.get_doc(dict( doctype='User', email='test-user-for-home-page@example.com', - first_name='test')).insert() + first_name='test')).insert(ignore_if_duplicate=True) role = frappe.get_doc(dict( doctype = 'Role', role_name = 'home-page-test', desk_access = 0, - home_page = '/home-page-test' - )).insert() + )).insert(ignore_if_duplicate=True) user.add_roles(role.name) user.save() + frappe.db.set_value('Role', 'home-page-test', 'home_page', 'home-page-test') frappe.set_user('test-user-for-home-page@example.com') self.assertEqual(get_home_page(), 'home-page-test') frappe.set_user('Administrator') - role.home_page = '' - role.save() + frappe.db.set_value('Role', 'home-page-test', 'home_page', '') # home page via portal settings frappe.db.set_value('Portal Settings', None, 'default_portal_home', 'test-portal-home') @@ -43,10 +44,45 @@ class TestWebsite(unittest.TestCase): frappe.cache().hdel('home_page', frappe.session.user) self.assertEqual(get_home_page(), 'test-portal-home') - def test_page_load(self): + frappe.db.set_value("Portal Settings", None, "default_portal_home", '') + clear_website_cache() + + # home page via website settings + frappe.db.set_value("Website Settings", None, "home_page", 'contact') + self.assertEqual(get_home_page(), 'contact') + + frappe.db.set_value("Website Settings", None, "home_page", None) + clear_website_cache() + + # fallback homepage + self.assertEqual(get_home_page(), 'me') + + # fallback homepage for guest frappe.set_user('Guest') + self.assertEqual(get_home_page(), 'login') + frappe.set_user('Administrator') + + # test homepage via hooks + clear_website_cache() + set_home_page_hook('get_website_user_home_page', 'frappe.www._test._test_home_page.get_website_user_home_page') + self.assertEqual(get_home_page(), '_test/_test_folder') + + clear_website_cache() + set_home_page_hook('website_user_home_page', 'login') + self.assertEqual(get_home_page(), 'login') + + clear_website_cache() + set_home_page_hook('home_page', 'about') + self.assertEqual(get_home_page(), 'about') + + clear_website_cache() + set_home_page_hook('role_home_page', {'home-page-test': 'home-page-test'}) + self.assertEqual(get_home_page(), 'home-page-test') + + + def test_page_load(self): set_request(method='POST', path='login') - response = render.render() + response = get_response() self.assertEqual(response.status_code, 200) @@ -54,14 +90,52 @@ class TestWebsite(unittest.TestCase): self.assertTrue('// login.js' in html) self.assertTrue('' in html) + + def test_static_page(self): + set_request(method='GET', path='/_test/static-file-test.png') + response = get_response() + self.assertEqual(response.status_code, 200) + + def test_error_page(self): + set_request(method='GET', path='/_test/problematic_page') + response = get_response() + self.assertEqual(response.status_code, 500) + + def test_login(self): + set_request(method='GET', path='/login') + response = get_response() + self.assertEqual(response.status_code, 200) + + html = frappe.safe_decode(response.get_data()) + + self.assertTrue('// login.js' in html) + self.assertTrue('' in html) + + def test_app(self): frappe.set_user('Administrator') + set_request(method='GET', path='/app') + response = get_response() + self.assertEqual(response.status_code, 200) + + html = frappe.safe_decode(response.get_data()) + self.assertTrue('window.app = true;' in html) + frappe.local.session_obj = None + + def test_not_found(self): + set_request(method='GET', path='/_test/missing') + response = get_response() + self.assertEqual(response.status_code, 404) + def test_redirect(self): import frappe.hooks + frappe.set_user('Administrator') + frappe.hooks.website_redirects = [ dict(source=r'/testfrom', target=r'://testto1'), dict(source=r'/testfromregex.*', target=r'://testto2'), - dict(source=r'/testsub/(.*)', target=r'://testto3/\1') + dict(source=r'/testsub/(.*)', target=r'://testto3/\1'), + dict(source=r'/courses/course\?course=(.*)', target=r'/courses/\1', match_with_query_string=True), ] website_settings = frappe.get_doc('Website Settings') @@ -71,32 +145,160 @@ class TestWebsite(unittest.TestCase): }) website_settings.save() - frappe.cache().delete_key('app_hooks') - frappe.cache().delete_key('website_redirects') - set_request(method='GET', path='/testfrom') - response = render.render() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), r'://testto1') set_request(method='GET', path='/testfromregex/test') - response = render.render() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), r'://testto2') set_request(method='GET', path='/testsub/me') - response = render.render() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), r'://testto3/me') set_request(method='GET', path='/test404') - response = render.render() + response = get_response() self.assertEqual(response.status_code, 404) set_request(method='GET', path='/testsource') - response = render.render() + response = get_response() self.assertEqual(response.status_code, 301) self.assertEqual(response.headers.get('Location'), '/testtarget') + set_request(method='GET', path='/courses/course?course=data') + response = get_response() + self.assertEqual(response.status_code, 301) + self.assertEqual(response.headers.get('Location'), '/courses/data') + delattr(frappe.hooks, 'website_redirects') frappe.cache().delete_key('app_hooks') + + def test_custom_page_renderer(self): + import frappe.hooks + frappe.hooks.page_renderer = ['frappe.tests.test_website.CustomPageRenderer'] + frappe.cache().delete_key('app_hooks') + set_request(method='GET', path='/custom') + response = get_response() + self.assertEqual(response.status_code, 3984) + + set_request(method='GET', path='/new') + content = get_response_content() + self.assertIn("
    Custom Page Response
    ", content) + + set_request(method='GET', path='/random') + response = get_response() + self.assertEqual(response.status_code, 404) + + delattr(frappe.hooks, 'page_renderer') + frappe.cache().delete_key('app_hooks') + + def test_printview_page(self): + content = get_response_content('/Language/en') + self.assertIn(' {% endif %} + {% if not disable_feedback %} +
    + {% include 'templates/includes/feedback/feedback.html' %} +
    + {% endif %}
    ') + + return html + + def post_process_context(self): + self.tags = MetaTags(self.path, self.context).tags + self.context.metatags = self.tags + self.set_base_template_if_missing() + self.set_title_with_prefix() + self.update_website_context() + # context sends us a new template path + self.template_path = self.context.template or self.template_path + self.context._context_dict = self.context + self.set_missing_values() + + def set_base_template_if_missing(self): + if not self.context.base_template_path: + app_base = frappe.get_hooks("base_template") + self.context.base_template_path = app_base[-1] if app_base else "templates/base.html" + + def set_title_with_prefix(self): + if (self.context.title_prefix and self.context.title + and not self.context.title.startswith(self.context.title_prefix)): + self.context.title = '{0} - {1}'.format(self.context.title_prefix, self.context.title) + + def set_missing_values(self): + # set using frappe.respond_as_web_page + if hasattr(frappe.local, 'response') and frappe.local.response.get('context'): + self.context.update(frappe.local.response.context) + + # to be able to inspect the context dict + # Use the macro "inspect" from macros.html + self.context.canonical = frappe.utils.get_url(frappe.utils.escape_html(self.path)) + + if "url_prefix" not in self.context: + self.context.url_prefix = "" + + if self.context.url_prefix and self.context.url_prefix[-1]!='/': + self.context.url_prefix += '/' + + self.context.path = self.path + self.context.pathname = frappe.local.path if hasattr(frappe, 'local') else self.path + + def update_website_context(self): + # apply context from hooks + update_website_context = frappe.get_hooks('update_website_context') + for method in update_website_context: + values = frappe.get_attr(method)(self.context) + if values: + self.context.update(values) diff --git a/frappe/website/page_renderers/document_page.py b/frappe/website/page_renderers/document_page.py new file mode 100644 index 0000000000..6b8d973ead --- /dev/null +++ b/frappe/website/page_renderers/document_page.py @@ -0,0 +1,92 @@ +import frappe +from frappe.model.document import get_controller +from frappe.website.page_renderers.base_template_page import BaseTemplatePage +from frappe.website.utils import cache_html +from frappe.website.router import (get_doctypes_with_web_view, + get_page_info_from_web_page_with_dynamic_routes) + + +class DocumentPage(BaseTemplatePage): + def can_render(self): + ''' + Find a document with matching `route` from all doctypes with `has_web_view`=1 + ''' + if self.search_in_doctypes_with_web_view(): + return True + + if self.search_web_page_dynamic_routes(): + return True + + return False + + def search_in_doctypes_with_web_view(self): + for doctype in get_doctypes_with_web_view(): + filters = dict(route=self.path) + meta = frappe.get_meta(doctype) + condition_field = self.get_condition_field(meta) + + if condition_field: + filters[condition_field] = 1 + + try: + self.docname = frappe.db.get_value(doctype, filters, 'name') + if self.docname: + self.doctype = doctype + return True + except Exception as e: + if not frappe.db.is_missing_column(e): + raise e + + def search_web_page_dynamic_routes(self): + d = get_page_info_from_web_page_with_dynamic_routes(self.path) + if d: + self.doctype = 'Web Page' + self.docname = d.name + return True + else: + return False + + def render(self): + html = self.get_html() + html = self.add_csrf_token(html) + + return self.build_response(html) + + @cache_html + def get_html(self): + self.doc = frappe.get_doc(self.doctype, self.docname) + self.init_context() + self.update_context() + self.post_process_context() + html = frappe.get_template(self.template_path).render(self.context) + return html + + def update_context(self): + self.context.doc = self.doc + self.context.update(self.context.doc.as_dict()) + self.context.update(self.context.doc.get_page_info()) + + self.template_path = self.context.template or self.template_path + + if not self.template_path: + self.template_path = self.context.doc.meta.get_web_template() + + if hasattr(self.doc, "get_context"): + ret = self.doc.get_context(self.context) + + if ret: + self.context.update(ret) + + for prop in ("no_cache", "sitemap"): + if prop not in self.context: + self.context[prop] = getattr(self.doc, prop, False) + + def get_condition_field(self, meta): + condition_field = None + if meta.is_published_field: + condition_field = meta.is_published_field + elif not meta.custom: + controller = get_controller(meta.name) + condition_field = controller.website.condition_field + + return condition_field diff --git a/frappe/website/page_renderers/error_page.py b/frappe/website/page_renderers/error_page.py new file mode 100644 index 0000000000..3501c77765 --- /dev/null +++ b/frappe/website/page_renderers/error_page.py @@ -0,0 +1,10 @@ +from frappe.website.page_renderers.template_page import TemplatePage + +class ErrorPage(TemplatePage): + def __init__(self, path=None, http_status_code=None, exception=None): + path = 'error' + super().__init__(path=path, http_status_code=http_status_code) + self.http_status_code = getattr(exception, 'http_status_code', None) or http_status_code or 500 + + def can_render(self): + return True diff --git a/frappe/website/page_renderers/list_page.py b/frappe/website/page_renderers/list_page.py new file mode 100644 index 0000000000..61c781ea14 --- /dev/null +++ b/frappe/website/page_renderers/list_page.py @@ -0,0 +1,11 @@ +import frappe +from frappe.website.page_renderers.template_page import TemplatePage + +class ListPage(TemplatePage): + def can_render(self): + return frappe.db.exists('DocType', self.path, True) + + def render(self): + frappe.local.form_dict.doctype = self.path + self.set_standard_path('list') + return super().render() diff --git a/frappe/website/page_renderers/not_found_page.py b/frappe/website/page_renderers/not_found_page.py new file mode 100644 index 0000000000..af510fecfc --- /dev/null +++ b/frappe/website/page_renderers/not_found_page.py @@ -0,0 +1,34 @@ +import os +from urllib.parse import urlparse + +import frappe +from frappe.website.page_renderers.template_page import TemplatePage +from frappe.website.utils import can_cache + +HOMEPAGE_PATHS = ('/', '/index', 'index') + +class NotFoundPage(TemplatePage): + def __init__(self, path, http_status_code): + self.request_path = path + self.request_url = frappe.local.request.url if hasattr(frappe.local, 'request') else '' + path = '404' + http_status_code = 404 + super().__init__(path=path, http_status_code=http_status_code) + + def can_render(self): + return True + + def render(self): + if self.can_cache_404(): + frappe.cache().hset('website_404', self.request_url, True) + return super().render() + + def can_cache_404(self): + # do not cache 404 for custom homepages + return can_cache() and self.request_url and not self.is_custom_home_page() + + def is_custom_home_page(self): + url_parts = urlparse(self.request_url) + request_url = os.path.splitext(url_parts.path)[0] + request_path = os.path.splitext(self.request_path)[0] + return request_url in HOMEPAGE_PATHS and request_path not in HOMEPAGE_PATHS diff --git a/frappe/website/page_renderers/not_permitted_page.py b/frappe/website/page_renderers/not_permitted_page.py new file mode 100644 index 0000000000..e69299f5c5 --- /dev/null +++ b/frappe/website/page_renderers/not_permitted_page.py @@ -0,0 +1,24 @@ +import frappe +from frappe import _ +from frappe.website.page_renderers.template_page import TemplatePage +from frappe.utils import cstr + +class NotPermittedPage(TemplatePage): + def __init__(self, path=None, http_status_code=None, exception=''): + frappe.local.message = cstr(exception) + super().__init__(path=path, http_status_code=http_status_code) + self.http_status_code = 403 + + def can_render(self): + return True + + def render(self): + frappe.local.message_title = _("Not Permitted") + frappe.local.response['context'] = dict( + indicator_color = 'red', + primary_action = '/login', + primary_label = _('Login'), + fullpage=True + ) + self.set_standard_path('message') + return super().render() diff --git a/frappe/website/page_renderers/print_page.py b/frappe/website/page_renderers/print_page.py new file mode 100644 index 0000000000..05d4026e2b --- /dev/null +++ b/frappe/website/page_renderers/print_page.py @@ -0,0 +1,23 @@ +import frappe +from frappe.website.page_renderers.template_page import TemplatePage + +class PrintPage(TemplatePage): + ''' + default path returns a printable object (based on permission) + /Quotation/Q-0001 + ''' + def can_render(self): + parts = self.path.split('/', 1) + if len(parts)==2: + if (frappe.db.exists('DocType', parts[0], True) + and frappe.db.exists(parts[0], parts[1], True)): + return True + + return False + + def render(self): + parts = self.path.split('/', 1) + frappe.form_dict.doctype = parts[0] + frappe.form_dict.name = parts[1] + self.set_standard_path('printview') + return super().render() diff --git a/frappe/website/page_renderers/redirect_page.py b/frappe/website/page_renderers/redirect_page.py new file mode 100644 index 0000000000..2049c375e8 --- /dev/null +++ b/frappe/website/page_renderers/redirect_page.py @@ -0,0 +1,16 @@ +import frappe +from frappe.website.utils import build_response + +class RedirectPage(object): + def __init__(self, path, http_status_code=301): + self.path = path + self.http_status_code = http_status_code + + def can_render(self): + return True + + def render(self): + return build_response(self.path, "", 301, { + "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), + "Cache-Control": "no-store, no-cache, must-revalidate" + }) diff --git a/frappe/website/page_renderers/static_page.py b/frappe/website/page_renderers/static_page.py new file mode 100644 index 0000000000..632e9b4302 --- /dev/null +++ b/frappe/website/page_renderers/static_page.py @@ -0,0 +1,41 @@ +import mimetypes +import os + +from werkzeug.wrappers import Response +from werkzeug.wsgi import wrap_file + +import frappe +from frappe.website.page_renderers.base_renderer import BaseRenderer + +UNSUPPORTED_STATIC_PAGE_TYPES = ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json') + +class StaticPage(BaseRenderer): + def __init__(self, path, http_status_code=None): + super().__init__(path=path, http_status_code=http_status_code) + self.set_file_path() + + def set_file_path(self): + self.file_path = '' + if not self.is_valid_file_path(): + return + for app in frappe.get_installed_apps(): + file_path = frappe.get_app_path(app, 'www') + '/' + self.path + if os.path.isfile(file_path): + self.file_path = file_path + + def can_render(self): + return self.is_valid_file_path() and self.file_path + + def is_valid_file_path(self): + if ('.' not in self.path): + return False + extension = self.path.rsplit('.', 1)[-1] + if extension in UNSUPPORTED_STATIC_PAGE_TYPES: + return False + return True + + def render(self): + f = open(self.file_path, 'rb') + response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) + response.mimetype = mimetypes.guess_type(self.file_path)[0] or 'application/octet-stream' + return response diff --git a/frappe/website/page_renderers/template_page.py b/frappe/website/page_renderers/template_page.py new file mode 100644 index 0000000000..249c998192 --- /dev/null +++ b/frappe/website/page_renderers/template_page.py @@ -0,0 +1,287 @@ +import io +import os +import click + +import frappe +from frappe.website.router import get_page_info +from frappe.website.page_renderers.base_template_page import BaseTemplatePage +from frappe.website.router import get_base_template +from frappe.website.utils import (extract_comment_tag, extract_title, get_next_link, + get_toc, get_frontmatter, cache_html, get_sidebar_items) + +WEBPAGE_PY_MODULE_PROPERTIES = ("base_template_path", "template", "no_cache", "sitemap", "condition_field") + +COMMENT_PROPERTY_KEY_VALUE_MAP = { + "no-breadcrumbs": ("no_breadcrumbs", 1), + "show-sidebar": ("show_sidebar", 1), + "add-breadcrumbs": ("add_breadcrumbs", 1), + "no-header": ("no_header", 1), + "add-next-prev-links": ("add_next_prev_links", 1), + "no-cache": ("no_cache", 1), + "no-sitemap": ("sitemap", 0), + "sitemap": ("sitemap", 1) +} + +class TemplatePage(BaseTemplatePage): + def __init__(self, path, http_status_code=None): + super().__init__(path=path, http_status_code=http_status_code) + self.set_template_path() + + def set_template_path(self): + ''' + Searches for file matching the path in the /www + and /templates/pages folders and sets path if match is found + ''' + folders = get_start_folders() + for app in frappe.get_installed_apps(frappe_last=True): + app_path = frappe.get_app_path(app) + + for dirname in folders: + search_path = os.path.join(app_path, dirname, self.path) + for file_path in self.get_index_path_options(search_path): + if os.path.isfile(file_path): + self.app = app + self.app_path = app_path + self.file_dir = dirname + self.basename = os.path.splitext(file_path)[0] + self.template_path = os.path.relpath(file_path, self.app_path) + self.basepath = os.path.dirname(file_path) + self.filename = os.path.basename(file_path) + self.name = os.path.splitext(self.filename)[0] + return + + def can_render(self): + return hasattr(self, 'template_path') and bool(self.template_path) + + @staticmethod + def get_index_path_options(search_path): + return (frappe.as_unicode(f'{search_path}{d}') for d in ('', '.html', '.md', '/index.html', '/index.md')) + + def render(self): + html = self.get_html() + html = self.add_csrf_token(html) + return self.build_response(html) + + @cache_html + def get_html(self): + # context object should be separate from self for security + # because it will be accessed via the user defined template + self.init_context() + + self.set_pymodule() + self.update_context() + self.setup_template() + self.load_colocated_files() + self.set_properties_from_source() + self.post_process_context() + + html = self.render_template() + html = self.update_toc(html) + + return html + + def post_process_context(self): + self.set_user_info() + self.add_sidebar_and_breadcrumbs() + super(TemplatePage, self).post_process_context() + + def add_sidebar_and_breadcrumbs(self): + if self.basepath: + self.context.sidebar_items = get_sidebar_items(self.context.website_sidebar, self.basepath) + + if self.context.add_breadcrumbs and not self.context.parents: + parent_path = os.path.dirname(self.path) + if self.path.endswith('index'): + # in case of index page move one directory up for parent path + parent_path = os.path.dirname(parent_path) + + for parent_file_path in self.get_index_path_options(parent_path): + parent_file_path = os.path.join(self.app_path, self.file_dir, parent_file_path) + if os.path.isfile(parent_file_path): + parent_page_context = get_page_info(parent_file_path, self.app, self.file_dir) + if parent_page_context: + self.context.parents = [dict(route=os.path.dirname(self.path), title=parent_page_context.title)] + break + + def set_pymodule(self): + ''' + A template may have a python module with a `get_context` method along with it in the + same folder. Also the hyphens will be coverted to underscore for python module names. + This method sets the pymodule_name if it exists. + ''' + template_basepath = os.path.splitext(self.template_path)[0] + self.pymodule_name = None + + # replace - with _ in the internal modules names + self.pymodule_path = os.path.join(os.path.dirname(template_basepath), os.path.basename(template_basepath.replace("-", "_")) + ".py") + + if os.path.exists(os.path.join(self.app_path, self.pymodule_path)): + self.pymodule_name = self.app + "." + self.pymodule_path.replace(os.path.sep, ".")[:-3] + + def setup_template(self): + '''Setup template source, frontmatter and markdown conversion''' + self.source = self.get_raw_template() + self.extract_frontmatter() + self.convert_from_markdown() + + def update_context(self): + self.set_page_properties() + self.set_properties_from_source() + self.context.build_version = frappe.utils.get_build_version() + + if self.pymodule_name: + self.pymodule = frappe.get_module(self.pymodule_name) + self.set_pymodule_properties() + + data = self.run_pymodule_method('get_context') + # some methods may return a "context" object + if data: + self.context.update(data) + # TODO: self.context.children = self.run_pymodule_method('get_children') + + self.context.developer_mode = frappe.conf.developer_mode + if self.context.http_status_code: + self.http_status_code = self.context.http_status_code + + def set_pymodule_properties(self): + for prop in WEBPAGE_PY_MODULE_PROPERTIES: + if hasattr(self.pymodule, prop): + self.context[prop] = getattr(self.pymodule, prop) + + def set_page_properties(self): + self.context.base_template = self.context.base_template \ + or get_base_template(self.path) + self.context.basepath = self.basepath + self.context.basename = self.basename + self.context.name = self.name + self.context.path = self.path + self.context.route = self.path + self.context.template = self.template_path + + def set_properties_from_source(self): + if not self.source: + return + context = self.context + if not context.title: + context.title = extract_title(self.source, self.path) + + base_template = extract_comment_tag(self.source, 'base_template') + if base_template: + context.base_template = base_template + + if (context.base_template + and "{%- extends" not in self.source + and "{% extends" not in self.source + and "" not in self.source): + self.source = '''{{% extends "{0}" %}} + {{% block page_content %}}{1}{{% endblock %}}'''.format(context.base_template, self.source) + + self.set_properties_via_comments() + + def set_properties_via_comments(self): + for comment, (context_key, value) in COMMENT_PROPERTY_KEY_VALUE_MAP.items(): + comment_tag = f"" + if comment_tag in self.source: + self.context[context_key] = value + click.echo(f'\n⚠️ DEPRECATION WARNING: {comment_tag} will be deprecated on 2021-12-31.') + click.echo(f'Please remove it from {self.template_path} in {self.app}') + + def run_pymodule_method(self, method_name): + if hasattr(self.pymodule, method_name): + try: + import inspect + method = getattr(self.pymodule, method_name) + if inspect.getfullargspec(method).args: + return method(self.context) + else: + return method() + except (frappe.PermissionError, frappe.DoesNotExistError, frappe.Redirect): + raise + except Exception: + if not frappe.flags.in_migrate: + frappe.errprint(frappe.utils.get_traceback()) + + def render_template(self): + if self.source: + html = frappe.render_template(self.source, self.context) + elif self.template_path: + if self.path.endswith('min.js'): + html = self.get_raw_template() # static + else: + html = frappe.get_template(self.template_path).render(self.context) + + return html + + def extends_template(self): + return (self.template_path.endswith(('.html', '.md')) + and ('{%- extends' in self.source + or '{% extends' in self.source)) + + def get_raw_template(self): + return frappe.get_jloader().get_source(frappe.get_jenv(), self.context.template)[0] + + def load_colocated_files(self): + '''load co-located css/js files with the same name''' + js_path = self.basename + '.js' + if os.path.exists(js_path) and '{% block script %}' not in self.source: + self.context.colocated_js = self.get_colocated_file(js_path) + + css_path = self.basename + '.css' + if os.path.exists(css_path) and '{% block style %}' not in self.source: + self.context.colocated_css = self.get_colocated_file(css_path) + + def get_colocated_file(self, path): + with io.open(path, 'r', encoding = 'utf-8') as f: + return f.read() + + def extract_frontmatter(self): + if not self.template_path.endswith(('.md', '.html')): + return + + try: + # values will be used to update self + res = get_frontmatter(self.source) + if res['attributes']: + self.context.update(res['attributes']) + self.source = res['body'] + except Exception: + pass + + def convert_from_markdown(self): + if self.template_path.endswith('.md'): + self.source = frappe.utils.md_to_html(self.source) + self.context.page_toc_html = self.source.toc_html + + if not self.context.show_sidebar: + self.source = '
    ' + self.source + '
    ' + + def update_toc(self, html): + if '{index}' in html: + html = html.replace('{index}', get_toc(self.path)) + + if '{next}' in html: + html = html.replace('{next}', get_next_link(self.path)) + + return html + + def set_standard_path(self, path): + self.app = 'frappe' + self.app_path = frappe.get_app_path('frappe') + self.path = path + self.template_path = 'www/{path}.html'.format(path=path) + + def set_missing_values(self): + super().set_missing_values() + # for backward compatibility + self.context.docs_base_url = '/docs' + + def set_user_info(self): + from frappe.utils.user import get_fullname_and_avatar + info = get_fullname_and_avatar(frappe.session.user) + self.context["fullname"] = info.fullname + self.context["user_image"] = info.avatar + self.context["user"] = info.name + + +def get_start_folders(): + return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') diff --git a/frappe/website/page_renderers/web_form.py b/frappe/website/page_renderers/web_form.py new file mode 100644 index 0000000000..786aeef3d1 --- /dev/null +++ b/frappe/website/page_renderers/web_form.py @@ -0,0 +1,10 @@ +from frappe.website.page_renderers.document_page import DocumentPage +import frappe + +class WebFormPage(DocumentPage): + def can_render(self): + webform_name = frappe.db.exists("Web Form", {'route': self.path}, cache=True) + if webform_name: + self.doctype = 'Web Form' + self.docname = webform_name + return bool(webform_name) diff --git a/frappe/website/path_resolver.py b/frappe/website/path_resolver.py new file mode 100644 index 0000000000..0ed097416e --- /dev/null +++ b/frappe/website/path_resolver.py @@ -0,0 +1,155 @@ +import re +import click + +from werkzeug.routing import Rule + +import frappe +from frappe.website.page_renderers.document_page import DocumentPage +from frappe.website.page_renderers.list_page import ListPage +from frappe.website.page_renderers.not_found_page import NotFoundPage +from frappe.website.page_renderers.print_page import PrintPage +from frappe.website.page_renderers.redirect_page import RedirectPage +from frappe.website.page_renderers.static_page import StaticPage +from frappe.website.page_renderers.template_page import TemplatePage +from frappe.website.page_renderers.web_form import WebFormPage +from frappe.website.router import evaluate_dynamic_routes +from frappe.website.utils import can_cache, get_home_page + + +class PathResolver(): + def __init__(self, path): + self.path = path.strip('/ ') + + def resolve(self): + '''Returns endpoint and a renderer instance that can render the endpoint''' + request = frappe._dict() + if hasattr(frappe.local, 'request'): + request = frappe.local.request or request + + # check if the request url is in 404 list + if request.url and can_cache() and frappe.cache().hget('website_404', request.url): + return self.path, NotFoundPage(self.path) + + try: + resolve_redirect(self.path, request.query_string) + except frappe.Redirect: + return frappe.flags.redirect_location, RedirectPage(self.path) + + endpoint = resolve_path(self.path) + custom_renderers = self.get_custom_page_renderers() + renderers = custom_renderers + [StaticPage, WebFormPage, DocumentPage, TemplatePage, ListPage, PrintPage, NotFoundPage] + + for renderer in renderers: + renderer_instance = renderer(endpoint, 200) + if renderer_instance.can_render(): + return endpoint, renderer_instance + + return endpoint, NotFoundPage(endpoint) + + def is_valid_path(self): + _endpoint, renderer_instance = self.resolve() + return not isinstance(renderer_instance, NotFoundPage) + + @staticmethod + def get_custom_page_renderers(): + custom_renderers = [] + for renderer_path in frappe.get_hooks('page_renderer') or []: + try: + renderer = frappe.get_attr(renderer_path) + if not hasattr(renderer, 'can_render'): + click.echo(f'{renderer.__name__} does not have can_render method') + continue + if not hasattr(renderer, 'render'): + click.echo(f'{renderer.__name__} does not have render method') + continue + + custom_renderers.append(renderer) + + except Exception: + click.echo(f'Failed to load page renderer. Import path: {renderer_path}') + + return custom_renderers + + + +def resolve_redirect(path, query_string=None): + ''' + Resolve redirects from hooks + + Example: + + website_redirect = [ + # absolute location + {"source": "/from", "target": "https://mysite/from"}, + + # relative location + {"source": "/from", "target": "/main"}, + + # use regex + {"source": r"/from/(.*)", "target": r"/main/\1"} + # use r as a string prefix if you use regex groups or want to escape any string literal + ] + ''' + redirects = frappe.get_hooks('website_redirects') + redirects += frappe.db.get_all('Website Route Redirect', ['source', 'target']) + + if not redirects: return + + redirect_to = frappe.cache().hget('website_redirects', path) + + if redirect_to: + frappe.flags.redirect_location = redirect_to + raise frappe.Redirect + + for rule in redirects: + pattern = rule['source'].strip('/ ') + '$' + path_to_match = path + if rule.get('match_with_query_string'): + path_to_match = path + '?' + frappe.safe_decode(query_string) + + if re.match(pattern, path_to_match): + redirect_to = re.sub(pattern, rule['target'], path_to_match) + frappe.flags.redirect_location = redirect_to + frappe.cache().hset('website_redirects', path_to_match, redirect_to) + raise frappe.Redirect + + +def resolve_path(path): + if not path: + path = "index" + + if path.endswith('.html'): + path = path[:-5] + + if path == "index": + path = get_home_page() + + frappe.local.path = path + + if path != "index": + path = resolve_from_map(path) + + return path + +def resolve_from_map(path): + '''transform dynamic route to a static one from hooks and route defined in doctype''' + rules = [Rule(r["from_route"], endpoint=r["to_route"], defaults=r.get("defaults")) + for r in get_website_rules()] + + return evaluate_dynamic_routes(rules, path) or path + +def get_website_rules(): + '''Get website route rules from hooks and DocType route''' + def _get(): + rules = frappe.get_hooks("website_route_rules") + for d in frappe.get_all('DocType', 'name, route', dict(has_web_view=1)): + if d.route: + rules.append(dict(from_route = '/' + d.route.strip('/'), to_route=d.name)) + + return rules + + if frappe.local.dev_server: + # dont cache in development + return _get() + + return frappe.cache().get_value('website_route_rules', _get) diff --git a/frappe/website/purifycss.py b/frappe/website/purifycss.py deleted file mode 100644 index 39e989db6e..0000000000 --- a/frappe/website/purifycss.py +++ /dev/null @@ -1,43 +0,0 @@ -from __future__ import print_function, unicode_literals -''' -Check for unused CSS Classes - -sUpdate source and target apps below and run from CLI - - bench --site [sitename] execute frappe.website.purifycss.purify.css - -''' - -import frappe, re, os - -source = frappe.get_app_path('frappe_theme', 'public', 'less', 'frappe_theme.less') -target_apps = ['erpnext_com', 'frappe_io', 'translator', 'chart_of_accounts_builder', 'frappe_theme'] - -def purifycss(): - with open(source, 'r') as f: - src = f.read() - - classes = [] - for line in src.splitlines(): - line = line.strip() - if not line: - continue - if line[0]=='@': - continue - classes.extend(re.findall('\.([^0-9][^ :&.{,(]*)', line)) - - classes = list(set(classes)) - - for app in target_apps: - for basepath, folders, files in os.walk(frappe.get_app_path(app)): - for fname in files: - if fname.endswith('.html') or fname.endswith('.md'): - #print 'checking {0}...'.format(fname) - with open(os.path.join(basepath, fname), 'r') as f: - src = f.read() - for c in classes: - if c in src: - classes.remove(c) - - for c in sorted(classes): - print(c) diff --git a/frappe/website/redirect.py b/frappe/website/redirect.py deleted file mode 100644 index 73e3c21727..0000000000 --- a/frappe/website/redirect.py +++ /dev/null @@ -1,41 +0,0 @@ -from __future__ import unicode_literals - -import re, frappe - -def resolve_redirect(path): - ''' - Resolve redirects from hooks - - Example: - - website_redirect = [ - # absolute location - {"source": "/from", "target": "https://mysite/from"}, - - # relative location - {"source": "/from", "target": "/main"}, - - # use regex - {"source": r"/from/(.*)", "target": r"/main/\1"} - # use r as a string prefix if you use regex groups or want to escape any string literal - ] - ''' - redirects = frappe.get_hooks('website_redirects') - redirects += frappe.db.get_all('Website Route Redirect', ['source', 'target']) - - if not redirects: return - - redirect_to = frappe.cache().hget('website_redirects', path) - - if redirect_to: - frappe.flags.redirect_location = redirect_to - raise frappe.Redirect - - for rule in redirects: - pattern = rule['source'].strip('/ ') + '$' - if re.match(pattern, path): - redirect_to = re.sub(pattern, rule['target'], path) - frappe.flags.redirect_location = redirect_to - frappe.cache().hset('website_redirects', path, redirect_to) - raise frappe.Redirect - diff --git a/frappe/website/render.py b/frappe/website/render.py deleted file mode 100644 index 71382c18ff..0000000000 --- a/frappe/website/render.py +++ /dev/null @@ -1,372 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -import frappe.sessions -from frappe.utils import cstr -import os, mimetypes, json -import re - -import six -from six import iteritems -from werkzeug.wrappers import Response -from werkzeug.routing import Rule -from werkzeug.wsgi import wrap_file - -from frappe.website.context import get_context -from frappe.website.redirect import resolve_redirect -from frappe.website.utils import (get_home_page, can_cache, delete_page_cache, - get_toc, get_next_link) -from frappe.website.router import clear_sitemap, evaluate_dynamic_routes -from frappe.translate import guess_language - -class PageNotFoundError(Exception): pass - -def render(path=None, http_status_code=None): - """render html page""" - if not path: - path = frappe.local.request.path - - try: - path = path.strip('/ ') - raise_if_disabled(path) - resolve_redirect(path) - path = resolve_path(path) - data = None - - # if in list of already known 404s, send it - if can_cache() and frappe.cache().hget('website_404', frappe.request.url): - data = render_page('404') - http_status_code = 404 - elif is_static_file(path): - return get_static_file_response() - elif is_web_form(path): - data = render_web_form(path) - else: - try: - data = render_page_by_language(path) - except frappe.PageDoesNotExistError: - doctype, name = get_doctype_from_path(path) - if doctype and name: - path = "printview" - frappe.local.form_dict.doctype = doctype - frappe.local.form_dict.name = name - elif doctype: - path = "list" - frappe.local.form_dict.doctype = doctype - else: - # 404s are expensive, cache them! - frappe.cache().hset('website_404', frappe.request.url, True) - data = render_page('404') - http_status_code = 404 - - if not data: - try: - data = render_page(path) - except frappe.PermissionError as e: - data, http_status_code = render_403(e, path) - - except frappe.PermissionError as e: - data, http_status_code = render_403(e, path) - - except frappe.Redirect as e: - raise e - - except Exception: - path = "error" - data = render_page(path) - http_status_code = 500 - - data = add_csrf_token(data) - - except frappe.Redirect: - return build_response(path, "", 301, { - "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), - "Cache-Control": "no-store, no-cache, must-revalidate" - }) - - return build_response(path, data, http_status_code or 200) - -def is_static_file(path): - if ('.' not in path): - return False - extn = path.rsplit('.', 1)[-1] - if extn in ('html', 'md', 'js', 'xml', 'css', 'txt', 'py', 'json'): - return False - - for app in frappe.get_installed_apps(): - file_path = frappe.get_app_path(app, 'www') + '/' + path - if os.path.exists(file_path): - frappe.flags.file_path = file_path - return True - - return False - -def is_web_form(path): - return bool(frappe.get_all("Web Form", filters={'route': path})) - -def render_web_form(path): - data = render_page(path) - return data - -def get_static_file_response(): - try: - f = open(frappe.flags.file_path, 'rb') - except IOError: - raise NotFound - - response = Response(wrap_file(frappe.local.request.environ, f), direct_passthrough=True) - response.mimetype = mimetypes.guess_type(frappe.flags.file_path)[0] or 'application/octet-stream' - return response - -def build_response(path, data, http_status_code, headers=None): - # build response - response = Response() - response.data = set_content_type(response, data, path) - response.status_code = http_status_code - response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") - response.headers["X-From-Cache"] = frappe.local.response.from_cache or False - - add_preload_headers(response) - if headers: - for key, val in iteritems(headers): - response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") - - return response - - -def add_preload_headers(response): - from bs4 import BeautifulSoup - - try: - preload = [] - soup = BeautifulSoup(response.data, "lxml") - for elem in soup.find_all('script', src=re.compile(".*")): - preload.append(("script", elem.get("src"))) - - for elem in soup.find_all('link', rel="stylesheet"): - preload.append(("style", elem.get("href"))) - - links = [] - for _type, link in preload: - links.append("<{}>; rel=preload; as={}".format(link, _type)) - - if links: - response.headers["Link"] = ",".join(links) - except Exception: - import traceback - traceback.print_exc() - - -def render_page_by_language(path): - translated_languages = frappe.get_hooks("translated_languages_for_website") - user_lang = guess_language(translated_languages) - if translated_languages and user_lang in translated_languages: - try: - if path and path != "index": - lang_path = '{0}/{1}'.format(user_lang, path) - else: - lang_path = user_lang # index - - return render_page(lang_path) - except frappe.DoesNotExistError: - return render_page(path) - - else: - return render_page(path) - -def render_page(path): - """get page html""" - out = None - - if can_cache(): - # return rendered page - page_cache = frappe.cache().hget("website_page", path) - if page_cache and frappe.local.lang in page_cache: - out = page_cache[frappe.local.lang] - - if out: - frappe.local.response.from_cache = True - return out - - return build(path) - -def build(path): - if not frappe.db: - frappe.connect() - - try: - return build_page(path) - except frappe.DoesNotExistError: - hooks = frappe.get_hooks() - if hooks.website_catch_all: - path = hooks.website_catch_all[0] - return build_page(path) - else: - raise - except Exception: - raise - -def build_page(path): - if not getattr(frappe.local, "path", None): - frappe.local.path = path - - context = get_context(path) - - if context.source: - html = frappe.render_template(context.source, context) - elif context.template: - if path.endswith('min.js'): - html = frappe.get_jloader().get_source(frappe.get_jenv(), context.template)[0] - else: - html = frappe.get_template(context.template).render(context) - - if '{index}' in html: - html = html.replace('{index}', get_toc(context.route)) - - if '{next}' in html: - html = html.replace('{next}', get_next_link(context.route)) - - # html = frappe.get_template(context.base_template_path).render(context) - - if can_cache(context.no_cache): - page_cache = frappe.cache().hget("website_page", path) or {} - page_cache[frappe.local.lang] = html - frappe.cache().hset("website_page", path, page_cache) - - return html - -def resolve_path(path): - if not path: - path = "index" - - if path.endswith('.html'): - path = path[:-5] - - if path == "index": - path = get_home_page() - - frappe.local.path = path - - if path != "index": - path = resolve_from_map(path) - - return path - -def resolve_from_map(path): - '''transform dynamic route to a static one from hooks and route defined in doctype''' - rules = [Rule(r["from_route"], endpoint=r["to_route"], defaults=r.get("defaults")) - for r in get_website_rules()] - - return evaluate_dynamic_routes(rules, path) or path - -def get_website_rules(): - '''Get website route rules from hooks and DocType route''' - def _get(): - rules = frappe.get_hooks("website_route_rules") - for d in frappe.get_all('DocType', 'name, route', dict(has_web_view=1)): - if d.route: - rules.append(dict(from_route = '/' + d.route.strip('/'), to_route=d.name)) - - return rules - - if frappe.local.dev_server: - # dont cache in development - return _get() - - return frappe.cache().get_value('website_route_rules', _get) - -def set_content_type(response, data, path): - if isinstance(data, dict): - response.mimetype = 'application/json' - response.charset = 'utf-8' - data = json.dumps(data) - return data - - response.mimetype = 'text/html' - response.charset = 'utf-8' - - if "." in path: - content_type, encoding = mimetypes.guess_type(path) - if content_type: - response.mimetype = content_type - if encoding: - response.charset = encoding - - return data - -def clear_cache(path=None): - '''Clear website caches - - :param path: (optional) for the given path''' - for key in ('website_generator_routes', 'website_pages', - 'website_full_index', 'sitemap_routes'): - frappe.cache().delete_value(key) - - frappe.cache().delete_value("website_404") - if path: - frappe.cache().hdel('website_redirects', path) - delete_page_cache(path) - else: - clear_sitemap() - frappe.clear_cache("Guest") - for key in ('portal_menu_items', 'home_page', 'website_route_rules', - 'doctypes_with_web_view', 'website_redirects', 'page_context', - 'website_page'): - frappe.cache().delete_value(key) - - for method in frappe.get_hooks("website_clear_cache"): - frappe.get_attr(method)(path) - -def render_403(e, pathname): - frappe.local.message = cstr(e.message if six.PY2 else e) - frappe.local.message_title = _("Not Permitted") - frappe.local.response['context'] = dict( - indicator_color = 'red', - primary_action = '/login', - primary_label = _('Login'), - fullpage=True - ) - return render_page("message"), e.http_status_code - -def get_doctype_from_path(path): - doctypes = frappe.db.sql_list("select name from tabDocType") - - parts = path.split("/") - - doctype = parts[0] - name = parts[1] if len(parts) > 1 else None - - if doctype in doctypes: - return doctype, name - - # try scrubbed - doctype = doctype.replace("_", " ").title() - if doctype in doctypes: - return doctype, name - - return None, None - -def add_csrf_token(data): - if frappe.local.session: - return data.replace("", ''.format( - frappe.local.session.data.csrf_token)) - else: - return data - -def raise_if_disabled(path): - routes = frappe.db.get_all('Portal Menu Item', - fields=['route', 'enabled'], - filters={ - 'enabled': 0, - 'route': ['like', '%{0}'.format(path)] - } - ) - - for r in routes: - _path = r.route.lstrip('/') - if path == _path and not r.enabled: - raise frappe.PermissionError - diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index b7e0aab27b..d141972679 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -1,7 +1,6 @@ # Copyright (c) 2013, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals import frappe from datetime import datetime from frappe.utils import getdate diff --git a/frappe/website/router.py b/frappe/website/router.py index f3518e179e..9809a73e48 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -1,138 +1,14 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import io import os import re import frappe -from frappe.model.document import get_controller -from frappe.website.utils import can_cache, delete_page_cache, extract_comment_tag, extract_title +from frappe.website.utils import extract_title from werkzeug.routing import Map, Rule, NotFound -def resolve_route(path): - """Returns the page route object based on searching in pages and generators. - The `www` folder is also a part of generator **Web Page**. - - The only exceptions are `/about` and `/contact` these will be searched in Web Pages - first before checking the standard pages.""" - - if path not in ("about", "contact"): - context = get_page_info_from_template(path) - if context: - return context - return get_page_context_from_doctype(path) - else: - context = get_page_context_from_doctype(path) - if context: - return context - return get_page_info_from_template(path) - -def get_page_context(path): - page_context = None - if can_cache(): - page_context_cache = frappe.cache().hget("page_context", path) or {} - page_context = page_context_cache.get(frappe.local.lang, None) - - if not page_context: - page_context = make_page_context(path) - if can_cache(page_context.no_cache): - page_context_cache[frappe.local.lang] = page_context - frappe.cache().hset("page_context", path, page_context_cache) - - return page_context - -def make_page_context(path): - context = resolve_route(path) - if not context: - raise frappe.PageDoesNotExistError - - context.doctype = context.ref_doctype - - if context.page_title: - context.title = context.page_title - - context.pathname = frappe.local.path - - return context - -def get_page_info_from_template(path): - '''Return page_info from path''' - for app in frappe.get_installed_apps(frappe_last=True): - app_path = frappe.get_app_path(app) - - folders = get_start_folders() - - for start in folders: - search_path = os.path.join(app_path, start, path) - options = (search_path, search_path + '.html', search_path + '.md', - search_path + '/index.html', search_path + '/index.md') - for o in options: - option = frappe.as_unicode(o) - if os.path.exists(option) and not os.path.isdir(option): - return get_page_info(option, app, start, app_path=app_path) - - return None - -def get_page_context_from_doctype(path): - page_info = get_page_info_from_doctypes(path) - if not page_info: - page_info = get_page_info_from_web_page_with_dynamic_routes(path) - - if page_info: - return frappe.get_doc(page_info.get("doctype"), - page_info.get("name")).get_page_info() - -def clear_sitemap(): - delete_page_cache("*") - -def get_all_page_context_from_doctypes(): - ''' - Get all doctype generated routes (for sitemap.xml) - ''' - routes = frappe.cache().get_value("website_generator_routes") - if not routes: - routes = get_page_info_from_doctypes() - frappe.cache().set_value("website_generator_routes", routes) - - return routes - -def get_page_info_from_doctypes(path=None): - ''' - Find a document with matching `route` from all doctypes with `has_web_view`=1 - ''' - routes = {} - for doctype in get_doctypes_with_web_view(): - filters = {} - controller = get_controller(doctype) - meta = frappe.get_meta(doctype) - - condition_field = (meta.is_published_field or - # custom doctypes dont have controllers and no website attribute - (controller.website.condition_field if not meta.custom else None)) - - if condition_field: - filters[condition_field] = 1 - - if path: - filters['route'] = path - - try: - for r in frappe.get_all(doctype, fields = ['name', 'route', 'modified'], - filters = filters, limit = 1): - - routes[r.route] = {"doctype": doctype, "name": r.name, "modified": r.modified} - - # just want one path, return it! - if path: - return routes[r.route] - except Exception as e: - if not frappe.db.is_missing_column(e): raise e - - return routes - def get_page_info_from_web_page_with_dynamic_routes(path): ''' Query Web Page with dynamic_route = 1 and evaluate if any of the routes match @@ -231,7 +107,7 @@ def get_page_info(path, app, start, basepath=None, app_path=None, fname=None): if basepath is None: basepath = os.path.dirname(path) - page_name, extn = fname.rsplit(".", 1) + page_name, extn = os.path.splitext(fname) # add website route page_info = frappe._dict() @@ -267,14 +143,12 @@ def get_page_info(path, app, start, basepath=None, app_path=None, fname=None): # get the source setup_source(page_info) - # extract properties from HTML comments - load_properties_from_source(page_info) + if not page_info.title: + page_info.title = extract_title(page_info.source, page_info.route) # extract properties from controller attributes load_properties_from_controller(page_info) - page_info.build_version = frappe.utils.get_build_version() - return page_info def get_frontmatter(string): @@ -374,48 +248,8 @@ def setup_index(page_info): # load index.txt if loading all pages index_txt_path = os.path.join(page_info.basepath, 'index.txt') if os.path.exists(index_txt_path): - page_info.index = open(index_txt_path, 'r').read().splitlines() - -def load_properties_from_source(page_info): - '''Load properties like no_cache, title from source html''' - - if not page_info.title: - page_info.title = extract_title(page_info.source, page_info.route) - - base_template = extract_comment_tag(page_info.source, 'base_template') - if base_template: - page_info.base_template = base_template - - if (page_info.base_template - and "{%- extends" not in page_info.source - and "{% extends" not in page_info.source - and "" not in page_info.source): - page_info.source = '''{{% extends "{0}" %}} - {{% block page_content %}}{1}{{% endblock %}}'''.format(page_info.base_template, page_info.source) - - if "" in page_info.source: - page_info.no_breadcrumbs = 1 - - if "" in page_info.source: - page_info.show_sidebar = 1 - - if "" in page_info.source: - page_info.add_breadcrumbs = 1 - - if "" in page_info.source: - page_info.no_header = 1 - - if "" in page_info.source: - page_info.add_next_prev_links = 1 - - if "" in page_info.source: - page_info.no_cache = 1 - - if "" in page_info.source: - page_info.sitemap = 0 - - if "" in page_info.source: - page_info.sitemap = 1 + with open(index_txt_path, 'r') as f: + page_info.index = f.read().splitlines() def load_properties_from_controller(page_info): if not page_info.controller: return @@ -433,8 +267,10 @@ def get_doctypes_with_web_view(): def _get(): installed_apps = frappe.get_installed_apps() doctypes = frappe.get_hooks("website_generators") - doctypes += [d.name for d in frappe.get_all('DocType', 'name, module', - dict(has_web_view=1)) if frappe.local.module_app[frappe.scrub(d.module)] in installed_apps] + doctypes_with_web_view = frappe.get_all('DocType', fields=['name', 'module'], + filters=dict(has_web_view=1)) + module_app_map = frappe.local.module_app + doctypes += [d.name for d in doctypes_with_web_view if module_app_map.get(frappe.scrub(d.module)) in installed_apps] return doctypes return frappe.cache().get_value('doctypes_with_web_view', _get) diff --git a/frappe/website/serve.py b/frappe/website/serve.py new file mode 100644 index 0000000000..fe7fc77064 --- /dev/null +++ b/frappe/website/serve.py @@ -0,0 +1,29 @@ +import frappe +from frappe.website.page_renderers.error_page import ErrorPage +from frappe.website.page_renderers.not_permitted_page import NotPermittedPage +from frappe.website.page_renderers.redirect_page import RedirectPage +from frappe.website.path_resolver import PathResolver + + +def get_response(path=None, http_status_code=200): + """Resolves path and renders page""" + response = None + path = path or frappe.local.request.path + endpoint = path + + try: + path_resolver = PathResolver(path) + endpoint, renderer_instance = path_resolver.resolve() + response = renderer_instance.render() + except frappe.Redirect: + return RedirectPage(endpoint or path, http_status_code).render() + except frappe.PermissionError as e: + response = NotPermittedPage(endpoint, http_status_code, exception=e).render() + except Exception as e: + response = ErrorPage(exception=e).render() + + return response + +def get_response_content(path=None, http_status_code=200): + response = get_response(path, http_status_code) + return str(response.data, 'utf-8') diff --git a/frappe/website/utils.py b/frappe/website/utils.py index efa15bdb14..e218afe8c6 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -1,14 +1,18 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals -import functools -import re +import json +import mimetypes import os -import frappe +import re +from functools import wraps +import yaml from six import iteritems -from past.builtins import cmp +from werkzeug.wrappers import Response + +import frappe +from frappe import _ +from frappe.model.document import Document from frappe.utils import md_to_html @@ -31,6 +35,8 @@ def find_first_image(html): return None def can_cache(no_cache=False): + if frappe.flags.force_website_cache: + return True if frappe.conf.disable_website_cache or frappe.conf.developer_mode: return False if getattr(frappe.local, "no_cache", False): @@ -133,14 +139,8 @@ def get_home_page_via_hooks(): return home_page -def is_signup_enabled(): - if getattr(frappe.local, "is_signup_enabled", None) is None: - frappe.local.is_signup_enabled = True - if frappe.utils.cint(frappe.db.get_value("Website Settings", - "Website Settings", "disable_signup")): - frappe.local.is_signup_enabled = False - - return frappe.local.is_signup_enabled +def is_signup_disabled(): + return frappe.db.get_single_value('Website Settings', 'disable_signup', True) def cleanup_page_name(title): """make page name from title""" @@ -150,91 +150,15 @@ def cleanup_page_name(title): name = title.lower() name = re.sub(r'[~!@#$%^&*+()<>,."\'\?]', '', name) name = re.sub('[:/]', '-', name) - name = '-'.join(name.split()) - # replace repeating hyphens name = re.sub(r"(-)\1+", r"\1", name) - return name[:140] -def get_shade(color, percent): - color, color_format = detect_color_format(color) - r, g, b, a = color - - avg = (float(int(r) + int(g) + int(b)) / 3) - # switch dark and light shades - if avg > 128: - percent = -percent - - # stronger diff for darker shades - if percent < 25 and avg < 64: - percent = percent * 2 - - new_color = [] - for channel_value in (r, g, b): - new_color.append(get_shade_for_channel(channel_value, percent)) - - r, g, b = new_color - - return format_color(r, g, b, a, color_format) - - -def detect_color_format(color): - if color.startswith("rgba"): - color_format = "rgba" - color = [c.strip() for c in color[5:-1].split(",")] - - elif color.startswith("rgb"): - color_format = "rgb" - color = [c.strip() for c in color[4:-1].split(",")] + [1] - - else: - # assume hex - color_format = "hex" - - if color.startswith("#"): - color = color[1:] - - if len(color) == 3: - # hex in short form like #fff - color = "{0}{0}{1}{1}{2}{2}".format(*tuple(color)) - - color = [int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16), 1] - - return color, color_format - - -def get_shade_for_channel(channel_value, percent): - v = int(channel_value) + int(int('ff', 16) * (float(percent)/100)) - if v < 0: - v=0 - if v > 255: - v=255 - - return v - - -def format_color(r, g, b, a, color_format): - if color_format == "rgba": - return "rgba({0}, {1}, {2}, {3})".format(r, g, b, a) - - elif color_format == "rgb": - return "rgb({0}, {1}, {2})".format(r, g, b) - - else: - # assume hex - return "#{0}{1}{2}".format(convert_to_hex(r), convert_to_hex(g), convert_to_hex(b)) - - -def convert_to_hex(channel_value): - h = hex(channel_value)[2:] - - if len(h) < 2: - h = "0" + h - - return h +def get_shade(color, percent=None): + frappe.msgprint(_('get_shade method has been deprecated.')) + return color def abs_url(path): """Deconstructs and Reconstructs a URL into an absolute URL or a URL relative from root '/'""" @@ -293,7 +217,7 @@ def get_full_index(route=None, app=None): pages = get_pages(app=app) # make children map - for route, page_info in iteritems(pages): + for route, page_info in pages.items(): parent_route = os.path.dirname(route) if parent_route not in added: children_map.setdefault(parent_route, []).append(page_info) @@ -316,8 +240,7 @@ def get_full_index(route=None, app=None): added.append(child_route) # add remaining pages not in index.txt - _children = sorted(children, key = functools.cmp_to_key(lambda a, b: cmp( - os.path.basename(a.route), os.path.basename(b.route)))) + _children = sorted(children, key=lambda x: os.path.basename(x.route)) for child_route in _children: if child_route not in new_children: @@ -365,25 +288,6 @@ def extract_comment_tag(source, tag): return None -def add_missing_headers(): - '''Walk and add missing headers in docs (to be called from bench execute)''' - path = frappe.get_app_path('erpnext', 'docs') - for basepath, folders, files in os.walk(path): - for fname in files: - if fname.endswith('.md'): - with open(os.path.join(basepath, fname), 'r') as f: - content = frappe.as_unicode(f.read()) - - if not content.startswith('# ') and not '

    ' in content: - with open(os.path.join(basepath, fname), 'w') as f: - if fname=='index.md': - fname = os.path.basename(basepath) - else: - fname = fname[:-3] - h = fname.replace('_', ' ').replace('-', ' ').title() - content = '# {0}\n\n'.format(h) + content - f.write(content.encode('utf-8')) - def get_html_content_based_on_type(doc, fieldname, content_type): ''' Set content based on content_type @@ -399,3 +303,208 @@ def get_html_content_based_on_type(doc, fieldname, content_type): content = '' return content + + +def clear_cache(path=None): + '''Clear website caches + :param path: (optional) for the given path''' + for key in ('website_generator_routes', 'website_pages', + 'website_full_index', 'sitemap_routes'): + frappe.cache().delete_value(key) + + frappe.cache().delete_value("website_404") + if path: + frappe.cache().hdel('website_redirects', path) + delete_page_cache(path) + else: + clear_sitemap() + frappe.clear_cache("Guest") + for key in ('portal_menu_items', 'home_page', 'website_route_rules', + 'doctypes_with_web_view', 'website_redirects', 'page_context', + 'website_page'): + frappe.cache().delete_value(key) + + for method in frappe.get_hooks("website_clear_cache"): + frappe.get_attr(method)(path) + +def clear_website_cache(path=None): + clear_cache(path) + +def clear_sitemap(): + delete_page_cache("*") + +def get_frontmatter(string): + "Reference: https://github.com/jonbeebe/frontmatter" + frontmatter = "" + body = "" + result = re.compile(r'^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$', re.S | re.M).search(string) + if result: + frontmatter = result.group(1) + body = result.group(2) + + return { + "attributes": yaml.safe_load(frontmatter), + "body": body, + } + +def get_sidebar_items(parent_sidebar, basepath): + import frappe.www.list + sidebar_items = [] + + hooks = frappe.get_hooks('look_for_sidebar_json') + look_for_sidebar_json = hooks[0] if hooks else frappe.flags.look_for_sidebar + + if basepath and look_for_sidebar_json: + sidebar_items = get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json) + + if not sidebar_items and parent_sidebar: + sidebar_items = frappe.get_all('Website Sidebar Item', + filters=dict(parent=parent_sidebar), fields=['title', 'route', '`group`'], + order_by='idx asc') + + if not sidebar_items: + sidebar_items = get_portal_sidebar_items() + + return sidebar_items + + +def get_portal_sidebar_items(): + sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user) + if sidebar_items is None: + sidebar_items = [] + roles = frappe.get_roles() + portal_settings = frappe.get_doc('Portal Settings', 'Portal Settings') + + def add_items(sidebar_items, items): + for d in items: + if d.get('enabled') and ((not d.get('role')) or d.get('role') in roles): + sidebar_items.append(d.as_dict() if isinstance(d, Document) else d) + + if not portal_settings.hide_standard_menu: + add_items(sidebar_items, portal_settings.get('menu')) + + if portal_settings.custom_menu: + add_items(sidebar_items, portal_settings.get('custom_menu')) + + items_via_hooks = frappe.get_hooks('portal_menu_items') + if items_via_hooks: + for i in items_via_hooks: + i['enabled'] = 1 + add_items(sidebar_items, items_via_hooks) + + frappe.cache().hset('portal_menu_items', frappe.session.user, sidebar_items) + + return sidebar_items + +def get_sidebar_items_from_sidebar_file(basepath, look_for_sidebar_json): + sidebar_items = [] + sidebar_json_path = get_sidebar_json_path(basepath, look_for_sidebar_json) + if not sidebar_json_path: + return sidebar_items + + with open(sidebar_json_path, 'r') as sidebarfile: + try: + sidebar_json = sidebarfile.read() + sidebar_items = json.loads(sidebar_json) + except json.decoder.JSONDecodeError: + frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path) + + return sidebar_items + +def get_sidebar_json_path(path, look_for=False): + '''Get _sidebar.json path from directory path + :param path: path of the current diretory + :param look_for: if True, look for _sidebar.json going upwards from given path + :return: _sidebar.json path + ''' + if os.path.split(path)[1] == 'www' or path == '/' or not path: + return '' + + sidebar_json_path = os.path.join(path, '_sidebar.json') + if os.path.exists(sidebar_json_path): + return sidebar_json_path + else: + if look_for: + return get_sidebar_json_path(os.path.split(path)[0], look_for) + else: + return '' + +def cache_html(func): + @wraps(func) + def cache_html_decorator(*args, **kwargs): + if can_cache(): + html = None + page_cache = frappe.cache().hget("website_page", args[0].path) + if page_cache and frappe.local.lang in page_cache: + html = page_cache[frappe.local.lang] + if html: + frappe.local.response.from_cache = True + return html + html = func(*args, **kwargs) + context = args[0].context + if can_cache(context.no_cache): + page_cache = frappe.cache().hget("website_page", args[0].path) or {} + page_cache[frappe.local.lang] = html + frappe.cache().hset("website_page", args[0].path, page_cache) + + return html + + return cache_html_decorator + +def build_response(path, data, http_status_code, headers=None): + # build response + response = Response() + response.data = set_content_type(response, data, path) + response.status_code = http_status_code + response.headers["X-Page-Name"] = path.encode("ascii", errors="xmlcharrefreplace") + response.headers["X-From-Cache"] = frappe.local.response.from_cache or False + + add_preload_headers(response) + if headers: + for key, val in iteritems(headers): + response.headers[key] = val.encode("ascii", errors="xmlcharrefreplace") + + return response + +def set_content_type(response, data, path): + if isinstance(data, dict): + response.mimetype = 'application/json' + response.charset = 'utf-8' + data = json.dumps(data) + return data + + response.mimetype = 'text/html' + response.charset = 'utf-8' + + # ignore paths ending with .com to avoid unnecessary download + # https://bugs.python.org/issue22347 + if "." in path and not path.endswith('.com'): + content_type, encoding = mimetypes.guess_type(path) + if content_type: + response.mimetype = content_type + if encoding: + response.charset = encoding + + return data + +def add_preload_headers(response): + from bs4 import BeautifulSoup + + try: + preload = [] + soup = BeautifulSoup(response.data, "lxml") + for elem in soup.find_all('script', src=re.compile(".*")): + preload.append(("script", elem.get("src"))) + + for elem in soup.find_all('link', rel="stylesheet"): + preload.append(("style", elem.get("href"))) + + links = [] + for _type, link in preload: + links.append("<{}>; rel=preload; as={}".format(link, _type)) + + if links: + response.headers["Link"] = ",".join(links) + except Exception: + import traceback + traceback.print_exc() diff --git a/frappe/website/web_form/request_data/request_data.py b/frappe/website/web_form/request_data/request_data.py index 07616644d3..1224e3f095 100644 --- a/frappe/website/web_form/request_data/request_data.py +++ b/frappe/website/web_form/request_data/request_data.py @@ -1,4 +1,2 @@ -from __future__ import unicode_literals - def get_context(context): pass diff --git a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.py b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.py index 74908f4119..75b748913a 100644 --- a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.py +++ b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - def get_context(context): # do your magic here pass \ No newline at end of file diff --git a/frappe/website/web_template/section_with_cards/section_with_cards.html b/frappe/website/web_template/section_with_cards/section_with_cards.html index 647923c9b1..d0eb164288 100644 --- a/frappe/website/web_template/section_with_cards/section_with_cards.html +++ b/frappe/website/web_template/section_with_cards/section_with_cards.html @@ -1,4 +1,4 @@ -{%- macro card(title, content, url) -%} +{%- macro card(title, content, url, image) -%} {%- set col_classes = resolve_class({ 'col-12 col-sm-6 col-lg-3': card_size == 'Small', 'col-12 col-sm-6 col-lg-4': card_size == 'Medium', @@ -11,6 +11,12 @@ }) -%}
    + {%- if image -%} + {{ frappe.render_template('templates/includes/image_with_blur.html', { + "src": image, + "class": ["card-img-top"] + }) }} + {%- endif -%}

    {{ title or '' }}

    {{ content or '' }}

    @@ -34,8 +40,9 @@ {%- set title = values['card_' + index + '_title'] -%} {%- set content = values['card_' + index + '_content'] -%} {%- set url = values['card_' + index + '_url'] -%} + {%- set image = values['card_' + index + '_image'] -%} {%- if title -%} - {{ card(title, content, url) }} + {{ card(title, content, url, image) }} {%- endif -%} {%- endfor -%}
    diff --git a/frappe/website/web_template/section_with_cards/section_with_cards.json b/frappe/website/web_template/section_with_cards/section_with_cards.json index 7c2c256396..c891119f97 100644 --- a/frappe/website/web_template/section_with_cards/section_with_cards.json +++ b/frappe/website/web_template/section_with_cards/section_with_cards.json @@ -41,6 +41,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_1_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_1_url", "fieldtype": "Data", @@ -65,6 +71,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_2_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_2_url", "fieldtype": "Data", @@ -89,6 +101,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_3_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_3_url", "fieldtype": "Data", @@ -113,6 +131,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_4_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_4_url", "fieldtype": "Data", @@ -137,6 +161,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_5_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_5_url", "fieldtype": "Data", @@ -161,6 +191,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_6_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_6_url", "fieldtype": "Data", @@ -185,6 +221,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_7_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_7_url", "fieldtype": "Data", @@ -209,6 +251,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_8_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_8_url", "fieldtype": "Data", @@ -233,6 +281,12 @@ "label": "Content", "reqd": 0 }, + { + "fieldname": "card_9_image", + "fieldtype": "Attach Image", + "label": "Image", + "reqd": 0 + }, { "fieldname": "card_9_url", "fieldtype": "Data", @@ -241,7 +295,7 @@ } ], "idx": 0, - "modified": "2020-10-26 17:42:53.356024", + "modified": "2021-05-03 13:26:34.470232", "modified_by": "Administrator", "module": "Website", "name": "Section with Cards", diff --git a/frappe/website/website_components/metatags.py b/frappe/website/website_components/metatags.py new file mode 100644 index 0000000000..045bef8fe1 --- /dev/null +++ b/frappe/website/website_components/metatags.py @@ -0,0 +1,68 @@ +import frappe + +class MetaTags(): + def __init__(self, path, context): + self.path = path + self.context = context + self.tags = frappe._dict(self.context.get("metatags") or {}) + self.init_metatags_from_context() + self.set_opengraph_tags() + self.set_twitter_tags() + self.set_meta_published_on() + self.set_metatags_from_website_route_meta() + + def init_metatags_from_context(self): + for key in ('title', 'description', 'image', 'author', 'url', 'published_on'): + if key not in self.tags and self.context.get(key): + self.tags[key] = self.context[key] + + if not self.tags.get('title'): + self.tags['title'] = self.context.get('name') + + if self.tags.get('image'): + self.tags['image'] = frappe.utils.get_url(self.tags['image']) + + self.tags["language"] = frappe.local.lang or "en" + + def set_opengraph_tags(self): + if "og:type" not in self.tags: + self.tags["og:type"] = "article" + + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['og:' + key] = self.tags.get(key) + + def set_twitter_tags(self): + for key in ('title', 'description', 'image', 'author', 'url'): + if self.tags.get(key): + self.tags['twitter:' + key] = self.tags.get(key) + + if self.tags.get('image'): + self.tags['twitter:card'] = "summary_large_image" + else: + self.tags["twitter:card"] = "summary" + + def set_meta_published_on(self): + if "published_on" in self.tags: + self.tags["datePublished"] = self.tags["published_on"] + del self.tags["published_on"] + + def set_metatags_from_website_route_meta(self): + ''' + Get meta tags from Website Route meta + they can override the defaults set above + ''' + route = self.path + if route == '': + # homepage + route = frappe.db.get_single_value('Website Settings', 'home_page') + + route_exists = (route + and not route.endswith(('.js', '.css')) + and frappe.db.exists('Website Route Meta', route)) + + if route_exists: + website_route_meta = frappe.get_doc('Website Route Meta', route) + for meta_tag in website_route_meta.meta_tags: + d = meta_tag.get_meta_dict() + self.tags.update(d) diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index fc08abeed9..3afe486944 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -1,11 +1,10 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.website.utils import cleanup_page_name -from frappe.website.render import clear_cache +from frappe.website.utils import clear_cache from frappe.modules import get_module_name from frappe.search.website_search import update_index_for_path, remove_document_from_index @@ -129,6 +128,8 @@ class WebsiteGenerator(Document): if not route.page_title: route.page_title = self.get(self.get_title_field()) + route.title = route.page_title + return route def send_indexing_request(self, operation_type='URL_UPDATED'): diff --git a/frappe/workflow/doctype/workflow/test_workflow.py b/frappe/workflow/doctype/workflow/test_workflow.py index 9ad0562a86..9bafd377fc 100644 --- a/frappe/workflow/doctype/workflow/test_workflow.py +++ b/frappe/workflow/doctype/workflow/test_workflow.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import frappe import unittest from frappe.utils import random_string diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index b4d7293fed..4959ee5639 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe import _ diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index 8c10af8251..b70ffb2406 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils.background_jobs import enqueue diff --git a/frappe/workflow/doctype/workflow_action_master/workflow_action_master.py b/frappe/workflow/doctype/workflow_action_master/workflow_action_master.py index df1123f786..b438a708dc 100644 --- a/frappe/workflow/doctype/workflow_action_master/workflow_action_master.py +++ b/frappe/workflow/doctype/workflow_action_master/workflow_action_master.py @@ -2,7 +2,6 @@ # Copyright (c) 2018, Frappe Technologies and contributors # For license information, please see license.txt -from __future__ import unicode_literals from frappe.model.document import Document class WorkflowActionMaster(Document): diff --git a/frappe/workflow/doctype/workflow_document_state/workflow_document_state.py b/frappe/workflow/doctype/workflow_document_state/workflow_document_state.py index 6e2ac212ae..ae7372f5c2 100644 --- a/frappe/workflow/doctype/workflow_document_state/workflow_document_state.py +++ b/frappe/workflow/doctype/workflow_document_state/workflow_document_state.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/workflow/doctype/workflow_state/test_workflow_state.py b/frappe/workflow/doctype/workflow_state/test_workflow_state.py index 63a2f9b151..eeb0807d52 100644 --- a/frappe/workflow/doctype/workflow_state/test_workflow_state.py +++ b/frappe/workflow/doctype/workflow_state/test_workflow_state.py @@ -1,7 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe test_records = frappe.get_test_records('Workflow State') \ No newline at end of file diff --git a/frappe/workflow/doctype/workflow_state/workflow_state.py b/frappe/workflow/doctype/workflow_state/workflow_state.py index c446a4445e..40f7d370dd 100644 --- a/frappe/workflow/doctype/workflow_state/workflow_state.py +++ b/frappe/workflow/doctype/workflow_state/workflow_state.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/workflow/doctype/workflow_transition/workflow_transition.py b/frappe/workflow/doctype/workflow_transition/workflow_transition.py index 4c07d700c6..e49a90f9d6 100644 --- a/frappe/workflow/doctype/workflow_transition/workflow_transition.py +++ b/frappe/workflow/doctype/workflow_transition/workflow_transition.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe from frappe.model.document import Document diff --git a/frappe/www/404.py b/frappe/www/404.py index c9de234743..1e6bdc177d 100644 --- a/frappe/www/404.py +++ b/frappe/www/404.py @@ -1,4 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals +def get_context(context): + context.http_status_code = 404 diff --git a/frappe/www/__init__.py b/frappe/www/__init__.py index baffc48825..8b13789179 100644 --- a/frappe/www/__init__.py +++ b/frappe/www/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals + diff --git a/frappe/www/_test/_sidebar.json b/frappe/www/_test/_sidebar.json new file mode 100644 index 0000000000..a2567e94da --- /dev/null +++ b/frappe/www/_test/_sidebar.json @@ -0,0 +1,6 @@ +[ + { + "route": "/_test/_test_folder", + "title": "Test Sidebar" + } +] diff --git a/frappe/patches/v5_0/__init__.py b/frappe/www/_test/_test_folder/__init__.py similarity index 100% rename from frappe/patches/v5_0/__init__.py rename to frappe/www/_test/_test_folder/__init__.py diff --git a/frappe/www/_test/_test_folder/_test_page.css b/frappe/www/_test/_test_folder/_test_page.css new file mode 100644 index 0000000000..e42b809085 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.css @@ -0,0 +1,3 @@ +body { + background-color: var(--bg-color); +} \ No newline at end of file diff --git a/frappe/www/_test/_test_folder/_test_page.html b/frappe/www/_test/_test_folder/_test_page.html new file mode 100644 index 0000000000..79bc96c568 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.html @@ -0,0 +1,6 @@ +{% extends base_template_path %} +{% block content %} +{% include "templates/includes/web_sidebar.html" %} +

    Test content

    +{next} +{% endblock %} diff --git a/frappe/www/_test/_test_folder/_test_page.js b/frappe/www/_test/_test_folder/_test_page.js new file mode 100644 index 0000000000..6e0c1f3a87 --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.js @@ -0,0 +1 @@ +console.log('test data'); \ No newline at end of file diff --git a/frappe/www/_test/_test_folder/_test_page.py b/frappe/www/_test/_test_folder/_test_page.py new file mode 100644 index 0000000000..3d4a645f9b --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_page.py @@ -0,0 +1,3 @@ +def get_context(context): + context.base_template_path = 'frappe/templates/test/_test_base_breadcrumbs.html' + context.add_breadcrumbs = 1 diff --git a/frappe/www/_test/_test_folder/_test_toc.md b/frappe/www/_test/_test_folder/_test_toc.md new file mode 100644 index 0000000000..02cc3c82be --- /dev/null +++ b/frappe/www/_test/_test_folder/_test_toc.md @@ -0,0 +1,19 @@ +--- +title: Test TOC +add_breadcrumbs: 1 +show_sidebar: 0 + +metatags: + description: Test Description. + keywords: Frappe Framework. +--- + +# Level 1 + +## Level 1.1 + +## Level 1.2 + +## Level 1.3 + +### Level 1.3.1 diff --git a/frappe/www/_test/_test_folder/index.md b/frappe/www/_test/_test_folder/index.md new file mode 100644 index 0000000000..ca8c55e9d5 --- /dev/null +++ b/frappe/www/_test/_test_folder/index.md @@ -0,0 +1,9 @@ +--- +title: Test Folder +add_breadcrumbs: 1 +show_sidebar: 1 +base_template: templates/web.html +--- +# Index + +{index} \ No newline at end of file diff --git a/frappe/patches/v5_2/__init__.py b/frappe/www/_test/_test_folder/new.csv/__init__.py similarity index 100% rename from frappe/patches/v5_2/__init__.py rename to frappe/www/_test/_test_folder/new.csv/__init__.py diff --git a/frappe/www/_test/_test_folder/new.csv/index.html b/frappe/www/_test/_test_folder/new.csv/index.html new file mode 100644 index 0000000000..7a1bb69558 --- /dev/null +++ b/frappe/www/_test/_test_folder/new.csv/index.html @@ -0,0 +1,12 @@ + + + + + + + Document + + + Test Page + + diff --git a/frappe/www/_test/_test_home_page.py b/frappe/www/_test/_test_home_page.py new file mode 100644 index 0000000000..936399c700 --- /dev/null +++ b/frappe/www/_test/_test_home_page.py @@ -0,0 +1,2 @@ +def get_website_user_home_page(user): + return '/_test/_test_folder' \ No newline at end of file diff --git a/frappe/www/_test/_test_no_context.html b/frappe/www/_test/_test_no_context.html new file mode 100644 index 0000000000..ba6f437dc1 --- /dev/null +++ b/frappe/www/_test/_test_no_context.html @@ -0,0 +1 @@ +{{ body }} \ No newline at end of file diff --git a/frappe/www/_test/_test_no_context.py b/frappe/www/_test/_test_no_context.py new file mode 100644 index 0000000000..2ecb1eb828 --- /dev/null +++ b/frappe/www/_test/_test_no_context.py @@ -0,0 +1,7 @@ +import frappe + +# no context object is accepted +def get_context(): + context = frappe._dict() + context.body = "Custom Content" + return context \ No newline at end of file diff --git a/frappe/www/_test/assets/css_asset.css b/frappe/www/_test/assets/css_asset.css new file mode 100644 index 0000000000..363a06397a --- /dev/null +++ b/frappe/www/_test/assets/css_asset.css @@ -0,0 +1 @@ +body{color:red} \ No newline at end of file diff --git a/frappe/www/_test/assets/js_asset.js b/frappe/www/_test/assets/js_asset.js new file mode 100644 index 0000000000..b39f5af3dd --- /dev/null +++ b/frappe/www/_test/assets/js_asset.js @@ -0,0 +1 @@ +console.log('in'); \ No newline at end of file diff --git a/frappe/www/_test/index.html b/frappe/www/_test/index.html new file mode 100644 index 0000000000..0dff60b400 --- /dev/null +++ b/frappe/www/_test/index.html @@ -0,0 +1 @@ +{index} \ No newline at end of file diff --git a/frappe/www/_test/problematic_page.html b/frappe/www/_test/problematic_page.html new file mode 100644 index 0000000000..5e194421d2 --- /dev/null +++ b/frappe/www/_test/problematic_page.html @@ -0,0 +1 @@ +{% raise %} diff --git a/frappe/www/_test/static-file-test.png b/frappe/www/_test/static-file-test.png new file mode 100644 index 0000000000..b51db82f82 Binary files /dev/null and b/frappe/www/_test/static-file-test.png differ diff --git a/frappe/www/about.py b/frappe/www/about.py index e7a7be23e8..05fe34b162 100644 --- a/frappe/www/about.py +++ b/frappe/www/about.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe sitemap = 1 diff --git a/frappe/www/app.py b/frappe/www/app.py index 5f19712cd3..27505c8131 100644 --- a/frappe/www/app.py +++ b/frappe/www/app.py @@ -1,10 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function - no_cache = 1 -base_template_path = "templates/www/app.html" import os, re import frappe diff --git a/frappe/www/complete_signup.py b/frappe/www/complete_signup.py index 00c499e786..0e57cb68c3 100644 --- a/frappe/www/complete_signup.py +++ b/frappe/www/complete_signup.py @@ -1,5 +1,3 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - diff --git a/frappe/www/contact.py b/frappe/www/contact.py index d976350075..53b0666be8 100644 --- a/frappe/www/contact.py +++ b/frappe/www/contact.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals - import frappe from frappe.utils import now from frappe import _ diff --git a/frappe/www/error.py b/frappe/www/error.py index 161038373d..11151ef766 100644 --- a/frappe/www/error.py +++ b/frappe/www/error.py @@ -1,12 +1,12 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt - -from __future__ import unicode_literals, print_function import frappe no_cache = 1 def get_context(context): if frappe.flags.in_migrate: return + context.http_status_code = 500 + print(frappe.get_traceback().encode("utf-8")) return {"error": frappe.get_traceback().replace("<", "<").replace(">", ">") } diff --git a/frappe/www/list.py b/frappe/www/list.py index fc4dc602c3..5e4e491c80 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -1,10 +1,9 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe, json from frappe.utils import cint, quoted -from frappe.website.render import resolve_path +from frappe.website.path_resolver import resolve_path from frappe.model.document import get_controller, Document from frappe import _ diff --git a/frappe/www/login.py b/frappe/www/login.py index 1ce25a81d9..6542b29d42 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -1,7 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals import frappe import frappe.utils from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys, login_via_oauth2, login_via_oauth2_id_token, login_oauth_user as _login_oauth_user, redirect_post_login diff --git a/frappe/www/me.html b/frappe/www/me.html index 402fdecf59..eb97c566d8 100644 --- a/frappe/www/me.html +++ b/frappe/www/me.html @@ -4,7 +4,6 @@ {% block header %}

    {{ _("My Account") }}

    {% endblock %} {% block page_content %} -