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 'JaneTest 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
${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: $('${__('Dear')} ${this.real_name},
-${__('Dear {0},', [this.real_name], 'Salutation in new email')},
+ ${SALUTATION_END_COMMENT}{0} ({1})
".format(feedback.feedback, feedback.rating) + + "".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] %}Test content
', content) + + def test_json_sidebar_data(self): + frappe.flags.look_for_sidebar = False + content = get_response_content('/_test/_test_folder/_test_page') + self.assertNotIn('Test Sidebar', content) + clear_website_cache() + frappe.flags.look_for_sidebar = True + content = get_response_content('/_test/_test_folder/_test_page') + self.assertIn('Test Sidebar', content) + frappe.flags.look_for_sidebar = False + + def test_base_template(self): + content = get_response_content('/_test/_test_custom_base.html') + + # assert the text in base template is rendered + self.assertIn('Test content
', content) + + def test_index_and_next_comment(self): + content = get_response_content('/_test/_test_folder') + # test if {index} was rendered + self.assertIn(' Test Page', content) + + self.assertIn('Test TOC', content) + + content = get_response_content('/_test/_test_folder/_test_page') + # test if {next} was rendered + self.assertIn('Next: Test TOC', content) + + def test_colocated_assets(self): + content = get_response_content('/_test/_test_folder/_test_page') + self.assertIn("", content) + self.assertIn("background-color: var(--bg-color);", content) + + def test_raw_assets_are_loaded(self): + content = get_response_content('/_test/assets/js_asset.js') + self.assertEqual("console.log('in');", content) + + content = get_response_content('/_test/assets/css_asset.css') + self.assertEqual("""body{color:red}""", content) + + def test_breadcrumbs(self): + content = get_response_content('/_test/_test_folder/_test_page') + self.assertIn('Test Folder', content) + self.assertIn(' Test Page', content) + + content = get_response_content('/_test/_test_folder/index') + self.assertIn(' Test', content) + self.assertIn('Test Folder', content) + + def test_get_context_without_context_object(self): + content = get_response_content('/_test/_test_no_context') + self.assertIn("Custom Content", content) + + def test_caching(self): + # to enable caching + frappe.flags.force_website_cache = True + + clear_website_cache() + # first response no-cache + response = get_response('/_test/_test_folder/_test_page') + self.assertIn(('X-From-Cache', 'False'), list(response.headers)) + + # first response returned from cache + response = get_response('/_test/_test_folder/_test_page') + self.assertIn(('X-From-Cache', 'True'), list(response.headers)) + + frappe.flags.force_website_cache = False + + +def set_home_page_hook(key, value): + from frappe import hooks + # reset home_page hooks + for hook in ('get_website_user_home_page','website_user_home_page','role_home_page','home_page'): + if hasattr(hooks, hook): + delattr(hooks, hook) + + setattr(hooks, key, value) + frappe.cache().delete_key('app_hooks') + +class CustomPageRenderer(): + def __init__(self, path, status_code=None): + self.path = path + # custom status code + self.status_code = 3984 + + def can_render(self): + if self.path in ('new', 'custom'): + return True + + def render(self): + return build_response(self.path, """|
", text)
+ return not re.search(r"
|
", text)
def get_sites(sites_path=None):
if not sites_path:
@@ -517,42 +520,18 @@ def get_sites(sites_path=None):
return sorted(sites)
-def get_request_session(max_retries=3):
+def get_request_session(max_retries=5):
import requests
from urllib3.util import Retry
+
session = requests.Session()
- session.mount("http://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500])))
- session.mount("https://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500])))
+ http_adapter = requests.adapters.HTTPAdapter(max_retries=Retry(total=max_retries, status_forcelist=[500]))
+
+ session.mount("http://", http_adapter)
+ session.mount("https://", http_adapter)
+
return session
-def watch(path, handler=None, debug=True):
- import time
-
- from watchdog.events import FileSystemEventHandler
- from watchdog.observers import Observer
-
- class Handler(FileSystemEventHandler):
- def on_any_event(self, event):
- if debug:
- print("File {0}: {1}".format(event.event_type, event.src_path))
-
- if not handler:
- print("No handler specified")
- return
-
- handler(event.src_path, event.event_type)
-
- event_handler = Handler()
- observer = Observer()
- observer.schedule(event_handler, path, recursive=True)
- observer.start()
- try:
- while True:
- time.sleep(1)
- except KeyboardInterrupt:
- observer.stop()
- observer.join()
-
def markdown(text, sanitize=True, linkify=True):
html = text if is_html(text) else frappe.utils.md_to_html(text)
@@ -609,7 +588,7 @@ def check_format(email_id):
def get_name_from_email_string(email_string, email_id, name):
name = email_string.replace(email_id, '')
- name = re.sub('[^A-Za-z0-9\u00C0-\u024F\/\_\' ]+', '', name).strip()
+ name = re.sub(r'[^A-Za-z0-9\u00C0-\u024F\/\_\' ]+', '', name).strip()
if not name:
name = email_id
return name
@@ -618,7 +597,7 @@ def get_installed_apps_info():
out = []
from frappe.utils.change_log import get_versions
- for app, version_details in iteritems(get_versions()):
+ for app, version_details in get_versions().items():
out.append({
'app_name': app,
'version': version_details.get('branch_version') or version_details.get('version'),
@@ -739,7 +718,7 @@ def get_safe_filters(filters):
try:
filters = json.loads(filters)
- if isinstance(filters, (integer_types, float)):
+ if isinstance(filters, (int, float)):
filters = frappe.as_unicode(filters)
except (TypeError, ValueError):
@@ -769,9 +748,9 @@ def set_request(**kwargs):
frappe.local.request = Request(builder.get_environ())
def get_html_for_route(route):
- from frappe.website import render
+ from frappe.website.serve import get_response
set_request(method='GET', path=route)
- response = render.render()
+ response = get_response()
html = frappe.safe_decode(response.get_data())
return html
@@ -809,7 +788,7 @@ def get_assets_json():
assets_json = None
if not assets_json:
- assets_json = frappe.read_file("assets/frappe/dist/assets.json")
+ assets_json = frappe.read_file("assets/assets.json")
cache.set_value("assets_json", assets_json, shared=True)
frappe.local.assets_json = frappe.safe_decode(assets_json)
diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py
index bd1f1154a9..8456835ca7 100755
--- a/frappe/utils/background_jobs.py
+++ b/frappe/utils/background_jobs.py
@@ -1,4 +1,3 @@
-from __future__ import unicode_literals, print_function
import redis
from rq import Connection, Queue, Worker
from rq.logutils import setup_loghandlers
@@ -7,11 +6,9 @@ from collections import defaultdict
import frappe
import os, socket, time
from frappe import _
-from six import string_types
from uuid import uuid4
import frappe.monitor
-# imports - third-party imports
default_timeout = 300
queue_timeout = {
@@ -89,7 +86,7 @@ def execute_job(site, method, event, job_name, kwargs, user=None, is_async=True,
if user:
frappe.set_user(user)
- if isinstance(method, string_types):
+ if isinstance(method, str):
method_name = method
method = frappe.get_attr(method)
else:
@@ -193,7 +190,7 @@ def get_queue_list(queue_list=None):
'''Defines possible queues. Also wraps a given queue in a list after validating.'''
default_queue_list = list(queue_timeout)
if queue_list:
- if isinstance(queue_list, string_types):
+ if isinstance(queue_list, str):
queue_list = [queue_list]
for queue in queue_list:
diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py
index b21efc5e89..908be52452 100644
--- a/frappe/utils/backups.py
+++ b/frappe/utils/backups.py
@@ -307,8 +307,8 @@ class BackupGenerator:
backup_summary = self.get_summary()
print("Backup Summary for {0} at {1}".format(frappe.local.site, now()))
- title = max([len(x) for x in backup_summary])
- path = max([len(x["path"]) for x in backup_summary.values()])
+ title = max(len(x) for x in backup_summary)
+ path = max(len(x["path"]) for x in backup_summary.values())
for _type, info in backup_summary.items():
template = "{{0:{0}}}: {{1:{1}}} {{2}}".format(title, path)
@@ -381,7 +381,7 @@ class BackupGenerator:
"",
])
- generated_header = "\n".join([f"-- {x}" for x in database_header_content]) + "\n"
+ generated_header = "\n".join(f"-- {x}" for x in database_header_content) + "\n"
with gzip.open(args.backup_path_db, "wt") as f:
f.write(generated_header)
diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py
index 2fb0bda058..b406c7e427 100644
--- a/frappe/utils/bench_helper.py
+++ b/frappe/utils/bench_helper.py
@@ -1,4 +1,3 @@
-from __future__ import unicode_literals, print_function
import click
import frappe
import os
@@ -101,4 +100,5 @@ def get_apps():
if __name__ == "__main__":
if not frappe._dev_server:
warnings.simplefilter('ignore')
+
main()
diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py
index d950d9f082..8dab9b748f 100755
--- a/frappe/utils/boilerplate.py
+++ b/frappe/utils/boilerplate.py
@@ -1,8 +1,5 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-
-from __future__ import unicode_literals, print_function
-
import frappe, os, re, git
from frappe.utils import touch_file, cstr
@@ -73,7 +70,7 @@ def make_boilerplate(dest, app_name):
f.write(frappe.as_unicode(setup_template.format(**hooks)))
with open(os.path.join(dest, hooks.app_name, "requirements.txt"), "w") as f:
- f.write("frappe")
+ f.write("# frappe -- https://github.com/frappe/frappe is installed via 'bench init'")
with open(os.path.join(dest, hooks.app_name, "README.md"), "w") as f:
f.write(frappe.as_unicode("## {0}\n\n{1}\n\n#### License\n\n{2}".format(hooks.app_title,
@@ -362,7 +359,6 @@ Configuration for docs
"""
# source_link = "https://github.com/[org_name]/{app_name}"
-# docs_base_url = "https://[org_name].github.io/{app_name}"
# headline = "App that does everything"
# sub_heading = "Yes, you got that right the first time, everything"
diff --git a/frappe/utils/bot.py b/frappe/utils/bot.py
index 45e1bd5a4e..c75b48ab49 100644
--- a/frappe/utils/bot.py
+++ b/frappe/utils/bot.py
@@ -1,8 +1,6 @@
# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-from __future__ import unicode_literals
-
import frappe, re, frappe.utils
from frappe.desk.notifications import get_notifications
from frappe import _
@@ -40,10 +38,10 @@ class BotParser(object):
def format_list(self, data):
'''Format list as markdown'''
- return _('I found these: ') + ', '.join([' [{title}](/app/Form/{doctype}/{name})'.format(
+ return _('I found these:') + ' ' + ', '.join(' [{title}](/app/Form/{doctype}/{name})'.format(
title = d.title or d.name,
doctype=self.get_doctype(),
- name=d.name) for d in data])
+ name=d.name) for d in data)
def get_doctype(self):
'''returns the doctype name from self.tables'''
@@ -58,8 +56,8 @@ class ShowNotificationBot(BotParser):
if open_items:
return ("Following items need your attention:\n\n"
- + "\n\n".join(["{0} [{1}](/app/List/{1})".format(d[1], d[0])
- for d in open_items if d[1] > 0]))
+ + "\n\n".join("{0} [{1}](/app/List/{1})".format(d[1], d[0])
+ for d in open_items if d[1] > 0))
else:
return 'Take it easy, nothing urgent needs your attention'
diff --git a/frappe/utils/change_log.py b/frappe/utils/change_log.py
index 33801af722..ddd11265eb 100644
--- a/frappe/utils/change_log.py
+++ b/frappe/utils/change_log.py
@@ -7,7 +7,6 @@ import subprocess # nosec
import requests
from semantic_version import Version
-from six.moves import range
import frappe
from frappe import _, safe_decode
@@ -119,9 +118,9 @@ def get_versions():
def get_app_branch(app):
'''Returns branch of an app'''
try:
- null_stream = open(os.devnull, 'wb')
- result = subprocess.check_output('cd ../apps/{0} && git rev-parse --abbrev-ref HEAD'.format(app),
- shell=True, stdin=null_stream, stderr=null_stream)
+ with open(os.devnull, 'wb') as null_stream:
+ result = subprocess.check_output(f'cd ../apps/{app} && git rev-parse --abbrev-ref HEAD',
+ shell=True, stdin=null_stream, stderr=null_stream)
result = safe_decode(result)
result = result.strip()
return result
@@ -130,9 +129,9 @@ def get_app_branch(app):
def get_app_last_commit_ref(app):
try:
- null_stream = open(os.devnull, 'wb')
- result = subprocess.check_output('cd ../apps/{0} && git rev-parse HEAD --short 7'.format(app),
- shell=True, stdin=null_stream, stderr=null_stream)
+ with open(os.devnull, 'wb') as null_stream:
+ result = subprocess.check_output(f'cd ../apps/{app} && git rev-parse HEAD --short 7',
+ shell=True, stdin=null_stream, stderr=null_stream)
result = safe_decode(result)
result = result.strip()
return result
diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py
index 1f57d8fbae..5640da666c 100644
--- a/frappe/utils/connections.py
+++ b/frappe/utils/connections.py
@@ -1,6 +1,6 @@
import socket
-from six.moves.urllib.parse import urlparse
+from urllib.parse import urlparse
from frappe import get_conf
REDIS_KEYS = ('redis_cache', 'redis_queue', 'redis_socketio')
diff --git a/frappe/utils/csvutils.py b/frappe/utils/csvutils.py
index 00163ade5f..734d68fe8a 100644
--- a/frappe/utils/csvutils.py
+++ b/frappe/utils/csvutils.py
@@ -1,14 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-
-from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
import json
import csv
-import six
import requests
-from six import StringIO, text_type, string_types
+from io import StringIO
from frappe.utils import encode, cstr, cint, flt, comma_or
def read_csv_content_from_uploaded_file(ignore_encoding=False):
@@ -40,11 +37,11 @@ def read_csv_content_from_attached_file(doc):
def read_csv_content(fcontent, ignore_encoding=False):
rows = []
- if not isinstance(fcontent, text_type):
+ if not isinstance(fcontent, str):
decoded = False
for encoding in ["utf-8", "windows-1250", "windows-1252"]:
try:
- fcontent = text_type(fcontent, encoding)
+ fcontent = str(fcontent, encoding)
decoded = True
break
except UnicodeDecodeError:
@@ -56,10 +53,7 @@ def read_csv_content(fcontent, ignore_encoding=False):
fcontent = fcontent.encode("utf-8")
content = [ ]
for line in fcontent.splitlines(True):
- if six.PY2:
- content.append(line)
- else:
- content.append(frappe.safe_decode(line))
+ content.append(frappe.safe_decode(line))
try:
rows = []
@@ -85,7 +79,7 @@ def read_csv_content(fcontent, ignore_encoding=False):
@frappe.whitelist()
def send_csv_to_client(args):
- if isinstance(args, string_types):
+ if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@@ -113,8 +107,6 @@ class UnicodeWriter:
self.writer = csv.writer(self.queue, quoting=quoting)
def writerow(self, row):
- if six.PY2:
- row = encode(row, self.encoding)
self.writer.writerow(row)
def getvalue(self):
diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py
index e386dcd881..ad61486113 100644
--- a/frappe/utils/dashboard.py
+++ b/frappe/utils/dashboard.py
@@ -1,6 +1,5 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
import frappe
from frappe import _
from functools import wraps
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 9cbac2a570..df36524c16 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1,14 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
-
import frappe
import operator
import json
import re, datetime, math, time
-from six.moves.urllib.parse import quote, urljoin
-from six import iteritems, text_type, string_types, integer_types
+from urllib.parse import quote, urljoin
from frappe.desk.utils import slug
DATE_FORMAT = "%Y-%m-%d"
@@ -72,7 +69,7 @@ def get_datetime(datetime_str=None):
def to_timedelta(time_str):
from dateutil import parser
- if isinstance(time_str, string_types):
+ if isinstance(time_str, str):
t = parser.parse(time_str)
return datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second, microseconds=t.microsecond)
@@ -91,7 +88,7 @@ def add_to_date(date, years=0, months=0, weeks=0, days=0, hours=0, minutes=0, se
if hours:
as_datetime = True
- if isinstance(date, string_types):
+ if isinstance(date, str):
as_string = True
if " " in date:
as_datetime = True
@@ -274,17 +271,17 @@ def get_time(time_str):
return parser.parse(time_str).time()
def get_datetime_str(datetime_obj):
- if isinstance(datetime_obj, string_types):
+ if isinstance(datetime_obj, str):
datetime_obj = get_datetime(datetime_obj)
return datetime_obj.strftime(DATETIME_FORMAT)
def get_date_str(date_obj):
- if isinstance(date_obj, string_types):
+ if isinstance(date_obj, str):
date_obj = get_datetime(date_obj)
return date_obj.strftime(DATE_FORMAT)
def get_time_str(timedelta_obj):
- if isinstance(timedelta_obj, string_types):
+ if isinstance(timedelta_obj, str):
timedelta_obj = to_timedelta(timedelta_obj)
hours, remainder = divmod(timedelta_obj.seconds, 3600)
@@ -457,7 +454,7 @@ def duration_to_seconds(duration):
def validate_duration_format(duration):
import re
- is_valid_duration = re.match("^(?:(\d+d)?((^|\s)\d+h)?((^|\s)\d+m)?((^|\s)\d+s)?)$", duration)
+ is_valid_duration = re.match(r"^(?:(\d+d)?((^|\s)\d+h)?((^|\s)\d+m)?((^|\s)\d+s)?)$", duration)
if not is_valid_duration:
frappe.throw(frappe._("Value {0} must be in the valid duration format: d h m s").format(frappe.bold(duration)))
@@ -549,7 +546,7 @@ def flt(s, precision=None):
>>> flt("a")
0.0
"""
- if isinstance(s, string_types):
+ if isinstance(s, str):
s = s.replace(',','')
try:
@@ -622,6 +619,26 @@ def ceil(s):
def cstr(s, encoding='utf-8'):
return frappe.as_unicode(s, encoding)
+def sbool(x):
+ """Converts str object to Boolean if possible.
+ Example:
+ "true" becomes True
+ "1" becomes True
+ "{}" remains "{}"
+
+ Args:
+ x (str): String to be converted to Bool
+
+ Returns:
+ object: Returns Boolean or type(x)
+ """
+ from distutils.util import strtobool
+
+ try:
+ return bool(strtobool(x))
+ except Exception:
+ return x
+
def rounded(num, precision=0):
"""round method for round halfs to nearest even algorithm aka banker's rounding - compatible with python3"""
precision = cint(precision)
@@ -686,12 +703,12 @@ def encode(obj, encoding="utf-8"):
if isinstance(obj, list):
out = []
for o in obj:
- if isinstance(o, text_type):
+ if isinstance(o, str):
out.append(o.encode(encoding))
else:
out.append(o)
return out
- elif isinstance(obj, text_type):
+ elif isinstance(obj, str):
return obj.encode(encoding)
else:
return obj
@@ -699,10 +716,10 @@ def encode(obj, encoding="utf-8"):
def parse_val(v):
"""Converts to simple datatypes from SQL query results"""
if isinstance(v, (datetime.date, datetime.datetime)):
- v = text_type(v)
+ v = str(v)
elif isinstance(v, datetime.timedelta):
- v = ":".join(text_type(v).split(":")[:2])
- elif isinstance(v, integer_types):
+ v = ":".join(str(v).split(":")[:2])
+ elif isinstance(v, int):
v = int(v)
return v
@@ -723,7 +740,7 @@ def fmt_money(amount, precision=None, currency=None, format=None):
# 40,000.00000 -> 40,000.00
# 40,000.23000 -> 40,000.23
- if isinstance(amount, string_types):
+ if isinstance(amount, str):
amount = flt(amount, precision)
if decimal_str:
@@ -939,7 +956,7 @@ def strip_html(text):
return _striptags_re.sub("", text)
def escape_html(text):
- if not isinstance(text, string_types):
+ if not isinstance(text, str):
return text
html_escape_table = {
@@ -962,7 +979,7 @@ def pretty_date(iso_datetime):
if not iso_datetime: return ''
import math
- if isinstance(iso_datetime, string_types):
+ if isinstance(iso_datetime, str):
iso_datetime = datetime.datetime.strptime(iso_datetime, DATETIME_FORMAT)
now_dt = datetime.datetime.strptime(now(), DATETIME_FORMAT)
dt_diff = now_dt - iso_datetime
@@ -1011,7 +1028,7 @@ def comma_and(some_list ,add_quotes=True):
def comma_sep(some_list, pattern, add_quotes=True):
if isinstance(some_list, (list, tuple)):
# list(some_list) is done to preserve the existing list
- some_list = [text_type(s) for s in list(some_list)]
+ some_list = [str(s) for s in list(some_list)]
if not some_list:
return ""
elif len(some_list) == 1:
@@ -1025,7 +1042,7 @@ def comma_sep(some_list, pattern, add_quotes=True):
def new_line_sep(some_list):
if isinstance(some_list, (list, tuple)):
# list(some_list) is done to preserve the existing list
- some_list = [text_type(s) for s in list(some_list)]
+ some_list = [str(s) for s in list(some_list)]
if not some_list:
return ""
elif len(some_list) == 1:
@@ -1111,7 +1128,7 @@ def get_link_to_report(name, label=None, report_type=None, doctype=None, filters
if filters:
conditions = []
- for k,v in iteritems(filters):
+ for k,v in filters.items():
if isinstance(v, list):
for value in v:
conditions.append(str(k)+'='+'["'+str(value[0]+'"'+','+'"'+str(value[1])+'"]'))
@@ -1167,7 +1184,7 @@ operator_map = {
def evaluate_filters(doc, filters):
'''Returns true if doc matches filters'''
if isinstance(filters, dict):
- for key, value in iteritems(filters):
+ for key, value in filters.items():
f = get_filter(None, {key:value})
if not compare(doc.get(f.fieldname), f.operator, f.value, f.fieldtype):
return False
@@ -1324,10 +1341,10 @@ def expand_relative_urls(html):
return "".join(to_expand)
- html = re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)((?!http)[^\'" >]+)([\'"]?)', _expand_relative_urls, html)
+ html = re.sub(r'(href|src){1}([\s]*=[\s]*[\'"]?)((?!http)[^\'" >]+)([\'"]?)', _expand_relative_urls, html)
# background-image: url('/assets/...')
- html = re.sub('(:[\s]?url)(\([\'"]?)((?!http)[^\'" >]+)([\'"]?\))', _expand_relative_urls, html)
+ html = re.sub(r'(:[\s]?url)(\([\'"]?)((?!http)[^\'" >]+)([\'"]?\))', _expand_relative_urls, html)
return html
def quoted(url):
@@ -1338,7 +1355,7 @@ def quote_urls(html):
groups = list(match.groups())
groups[2] = quoted(groups[2])
return "".join(groups)
- return re.sub('(href|src){1}([\s]*=[\s]*[\'"]?)((?:http)[^\'">]+)([\'"]?)',
+ return re.sub(r'(href|src){1}([\s]*=[\s]*[\'"]?)((?:http)[^\'">]+)([\'"]?)',
_quote_url, html)
def unique(seq):
@@ -1355,7 +1372,7 @@ def strip(val, chars=None):
def to_markdown(html):
from html2text import html2text
- from six.moves import html_parser as HTMLParser
+ from html.parser import HTMLParser
text = None
try:
@@ -1494,7 +1511,7 @@ def get_user_info_for_avatar(user_id):
return user_info
-class UnicodeWithAttrs(text_type):
+class UnicodeWithAttrs(str):
def __init__(self, text):
self.toc_html = text.toc_html
self.metadata = text.metadata
diff --git a/frappe/utils/dateutils.py b/frappe/utils/dateutils.py
index 06b434a512..2c2537da5f 100644
--- a/frappe/utils/dateutils.py
+++ b/frappe/utils/dateutils.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
import frappe.defaults
import datetime
from frappe.utils import get_datetime, add_to_date, getdate
from frappe.utils.data import get_first_day, get_first_day_of_week, get_quarter_start, get_year_start,\
get_last_day, get_last_day_of_week, get_quarter_ending, get_year_ending
-from six import string_types
# global values -- used for caching
dateformats = {
@@ -71,7 +69,7 @@ def get_user_date_format():
def datetime_in_user_format(date_time):
if not date_time:
return ""
- if isinstance(date_time, string_types):
+ if isinstance(date_time, str):
date_time = get_datetime(date_time)
from frappe.utils import formatdate
return formatdate(date_time.date()) + " " + date_time.strftime("%H:%M")
diff --git a/frappe/utils/doctor.py b/frappe/utils/doctor.py
index e97f792b88..9dafc4dd21 100644
--- a/frappe/utils/doctor.py
+++ b/frappe/utils/doctor.py
@@ -1,10 +1,8 @@
-from __future__ import unicode_literals, print_function
import frappe.utils
from collections import defaultdict
from rq import Worker, Connection
from frappe.utils.background_jobs import get_redis_conn, get_queue, get_queue_list
from frappe.utils.scheduler import is_scheduler_disabled, is_scheduler_inactive
-from six import iteritems
def get_workers():
@@ -130,7 +128,7 @@ def doctor(site=None):
print("Queue:", queue)
print("Number of Jobs: ", job_count[queue])
print("Methods:")
- for method, count in iteritems(jobs_per_queue[queue]):
+ for method, count in jobs_per_queue[queue].items():
print("{0} : {1}".format(method, count))
print("------------")
diff --git a/frappe/utils/error.py b/frappe/utils/error.py
index 2d8d6491a5..07e34674fe 100644
--- a/frappe/utils/error.py
+++ b/frappe/utils/error.py
@@ -2,8 +2,6 @@
# Copyright (c) 2015, Maxwell Morais and contributors
# For license information, please see license.txt
-from __future__ import unicode_literals
-
import os
import sys
import traceback
@@ -17,7 +15,7 @@ import pydoc
import cgitb
import datetime
import json
-import six
+
def make_error_snapshot(exception):
if frappe.conf.disable_error_snapshot:
@@ -51,7 +49,7 @@ def get_snapshot(exception, context=10):
"""
etype, evalue, etb = sys.exc_info()
- if isinstance(etype, six.class_types):
+ if isinstance(etype, type):
etype = etype.__name__
# creates a snapshot dict with some basic information
@@ -131,7 +129,7 @@ def get_snapshot(exception, context=10):
# add all local values (of last frame) to the snapshot
for name, value in locals.items():
- s['locals'][name] = value if isinstance(value, six.text_type) else pydoc.text.repr(value)
+ s['locals'][name] = value if isinstance(value, str) else pydoc.text.repr(value)
return s
@@ -215,7 +213,7 @@ def raise_error_on_no_output(error_message, error_type=None, keep_quiet=None):
>>> @raise_error_on_no_output("Ingradients missing")
... def get_indradients(_raise_error=1): return
...
- >>> get_indradients()
+ >>> get_ingradients()
`Exception Name`: Ingradients missing
"""
def decorator_raise_error_on_no_output(func):
diff --git a/frappe/utils/file_lock.py b/frappe/utils/file_lock.py
index b85ace7db9..8c65dd32ce 100644
--- a/frappe/utils/file_lock.py
+++ b/frappe/utils/file_lock.py
@@ -1,8 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
-
'''
File based locking utility
'''
diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py
index 2177e67274..30b0d816bb 100644
--- a/frappe/utils/file_manager.py
+++ b/frappe/utils/file_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
import os, base64, re, json
import hashlib
@@ -11,8 +10,7 @@ from frappe.utils import get_hook_method, get_files_path, random_string, encode,
from frappe import _
from frappe import conf
from copy import copy
-from six.moves.urllib.parse import unquote
-from six import text_type, PY2, string_types
+from urllib.parse import unquote
class MaxFileSizeReachedError(frappe.ValidationError):
@@ -123,7 +121,7 @@ def get_uploaded_content():
def save_file(fname, content, dt, dn, folder=None, decode=False, is_private=0, df=None):
if decode:
- if isinstance(content, text_type):
+ if isinstance(content, str):
content = content.encode("utf-8")
if b"," in content:
@@ -207,7 +205,7 @@ def write_file(content, fname, is_private=0):
# create directory (if not exists)
frappe.create_folder(file_path)
# write the file
- if isinstance(content, text_type):
+ if isinstance(content, str):
content = content.encode()
with open(os.path.join(file_path.encode('utf-8'), fname.encode('utf-8')), 'wb+') as f:
f.write(content)
@@ -297,18 +295,14 @@ def get_file(fname):
file_path = get_file_path(fname)
# 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 [file_path.rsplit("/", 1)[-1], content]
@@ -338,7 +332,7 @@ def get_file_path(file_name):
def get_content_hash(content):
- if isinstance(content, text_type):
+ if isinstance(content, str):
content = content.encode()
return hashlib.md5(content).hexdigest()
@@ -397,8 +391,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)
@@ -443,12 +437,12 @@ def validate_filename(filename):
@frappe.whitelist()
def add_attachments(doctype, name, attachments):
'''Add attachments to the given DocType'''
- if isinstance(attachments, string_types):
+ if isinstance(attachments, str):
attachments = json.loads(attachments)
# loop through attachments
files =[]
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
f = save_url(attach.file_url, attach.file_name, doctype, name, "Home/Attachments", attach.is_private)
diff --git a/frappe/utils/formatters.py b/frappe/utils/formatters.py
index 7913413878..9efccc15f0 100644
--- a/frappe/utils/formatters.py
+++ b/frappe/utils/formatters.py
@@ -1,18 +1,16 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
import frappe
import datetime
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration
from frappe.model.meta import get_field_currency, get_field_precision
import re
-from six import string_types
def format_value(value, df=None, doc=None, currency=None, translated=False):
'''Format value based on given fieldtype, document reference, currency reference.
If docfield info (df) is not given, it will try and guess based on the datatype of the value'''
- if isinstance(df, string_types):
+ if isinstance(df, str):
df = frappe._dict(fieldtype=df)
if not df:
diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py
index c20f3b29d4..efe92232d9 100644
--- a/frappe/utils/global_search.py
+++ b/frappe/utils/global_search.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
import re
import redis
@@ -11,7 +9,6 @@ import os
from frappe.utils import cint, strip_html_tags
from frappe.utils.html_utils import unescape_html
from frappe.model.base_document import get_controller
-from six import text_type
def setup_global_search_table():
"""
@@ -310,14 +307,14 @@ def get_routes_to_index():
def add_route_to_global_search(route):
from bs4 import BeautifulSoup
- from frappe.website.render import render_page
+ from frappe.website.serve import get_response_content
from frappe.utils import set_request
frappe.set_user('Guest')
frappe.local.no_cache = True
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 ''
@@ -332,7 +329,7 @@ def add_route_to_global_search(route):
route=route
)
sync_value_in_queue(value)
- except (frappe.PermissionError, frappe.DoesNotExistError, frappe.ValidationError, Exception):
+ except Exception:
pass
frappe.set_user('Administrator')
@@ -348,9 +345,9 @@ def get_formatted_value(value, field):
if getattr(field, 'fieldtype', None) in ["Text", "Text Editor"]:
value = unescape_html(frappe.safe_decode(value))
- value = (re.subn(r'(?s)<[\s]*(script|style).*?\1>', '', text_type(value))[0])
+ value = (re.subn(r'(?s)<[\s]*(script|style).*?\1>', '', str(value))[0])
value = ' '.join(value.split())
- return field.label + " : " + strip_html_tags(text_type(value))
+ return field.label + " : " + strip_html_tags(str(value))
def sync_global_search():
diff --git a/frappe/utils/goal.py b/frappe/utils/goal.py
index 4c63eb9fc4..195c962aab 100644
--- a/frappe/utils/goal.py
+++ b/frappe/utils/goal.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 _
diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py
index f3f86dcad2..ccb374fbcc 100644
--- a/frappe/utils/html_utils.py
+++ b/frappe/utils/html_utils.py
@@ -1,14 +1,15 @@
-from __future__ import unicode_literals
-import frappe
import json
import re
-import bleach_whitelist.bleach_whitelist as bleach_whitelist
-from six import string_types
+
+from bleach_allowlist import bleach_allowlist
+
+import frappe
+
def clean_html(html):
import bleach
- if not isinstance(html, string_types):
+ if not isinstance(html, str):
return html
return bleach.clean(clean_script_and_style(html),
@@ -21,7 +22,7 @@ def clean_html(html):
def clean_email_html(html):
import bleach
- if not isinstance(html, string_types):
+ if not isinstance(html, str):
return html
return bleach.clean(clean_script_and_style(html),
@@ -60,7 +61,7 @@ def sanitize_html(html, linkify=False):
import bleach
from bs4 import BeautifulSoup
- if not isinstance(html, string_types):
+ if not isinstance(html, str):
return html
elif is_json(html):
@@ -72,7 +73,7 @@ def sanitize_html(html, linkify=False):
tags = (acceptable_elements + svg_elements + mathml_elements
+ ["html", "head", "meta", "link", "body", "style", "o:p"])
attributes = {"*": acceptable_attributes, 'svg': svg_attributes}
- styles = bleach_whitelist.all_styles
+ styles = bleach_allowlist.all_styles
strip_comments = False
# returns html with escaped tags, escaped orphan >, <, etc.
diff --git a/frappe/utils/identicon.py b/frappe/utils/identicon.py
index 328fb03836..e570875b4a 100644
--- a/frappe/utils/identicon.py
+++ b/frappe/utils/identicon.py
@@ -1,9 +1,9 @@
-from __future__ import unicode_literals
+
from PIL import Image, ImageDraw
from hashlib import md5
import base64
import random
-from six import StringIO
+from io import StringIO
GRID_SIZE = 5
BORDER_SIZE = 20
diff --git a/frappe/utils/image.py b/frappe/utils/image.py
index 60595464a1..b6f4c67c44 100644
--- a/frappe/utils/image.py
+++ b/frappe/utils/image.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
import os
def resize_images(path, maxdim=700):
@@ -24,7 +22,7 @@ def strip_exif_data(content, content_type):
Works by creating a new Image object which ignores exif by
default and then extracts the binary data back into content.
- Returns:
+ Returns:
Bytes: Stripped image content
"""
@@ -33,11 +31,11 @@ def strip_exif_data(content, content_type):
original_image = Image.open(io.BytesIO(content))
output = io.BytesIO()
-
+
new_image = Image.new(original_image.mode, original_image.size)
new_image.putdata(list(original_image.getdata()))
new_image.save(output, format=content_type.split('/')[1])
-
+
content = output.getvalue()
return content
\ No newline at end of file
diff --git a/frappe/utils/install.py b/frappe/utils/install.py
index 93f46a2a16..91d8f04eb4 100644
--- a/frappe/utils/install.py
+++ b/frappe/utils/install.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 getpass
from frappe.utils.password import update_password
diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py
index a77eca4977..65ea2c20b8 100644
--- a/frappe/utils/jinja.py
+++ b/frappe/utils/jinja.py
@@ -1,7 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
-
def get_jenv():
import frappe
from frappe.utils.safe_exec import get_safe_globals
@@ -67,7 +65,7 @@ def render_template(template, context, is_path=None, safe_render=True):
:param safe_render: (optional) prevent server side scripting via jinja templating
'''
- from frappe import get_traceback, throw
+ from frappe import _, get_traceback, throw
from jinja2 import TemplateError
if not template:
@@ -77,7 +75,7 @@ def render_template(template, context, is_path=None, safe_render=True):
return get_jenv().get_template(template).render(context)
else:
if safe_render and ".__" in template:
- throw("Illegal template")
+ throw(_("Illegal template"))
try:
return get_jenv().from_string(template).render(context)
except TemplateError:
diff --git a/frappe/utils/jinja_globals.py b/frappe/utils/jinja_globals.py
index 347d52dc57..2c14249672 100644
--- a/frappe/utils/jinja_globals.py
+++ b/frappe/utils/jinja_globals.py
@@ -1,9 +1,6 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
-from frappe.utils.jinja import get_jenv
-
def resolve_class(classes):
if classes is None:
@@ -13,15 +10,17 @@ def resolve_class(classes):
return classes
if isinstance(classes, (list, tuple)):
- return " ".join([resolve_class(c) for c in classes]).strip()
+ return " ".join(resolve_class(c) for c in classes).strip()
if isinstance(classes, dict):
- return " ".join([classname for classname in classes if classes[classname]]).strip()
+ return " ".join(classname for classname in classes if classes[classname]).strip()
return classes
def inspect(var, render=True):
+ from frappe.utils.jinja import get_jenv
+
context = {"var": var}
if render:
html = "
{{ var | pprint | e }}"
diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py
index 364ffa776d..617572deb7 100755
--- a/frappe/utils/logger.py
+++ b/frappe/utils/logger.py
@@ -1,13 +1,9 @@
-# imports - compatibility imports
-from __future__ import unicode_literals
-
# imports - standard imports
import logging
import os
from logging.handlers import RotatingFileHandler
# imports - third party imports
-from six import text_type
# imports - module imports
import frappe
@@ -83,10 +79,10 @@ class SiteContextFilter(logging.Filter):
"""This is a filter which injects request information (if available) into the log."""
def filter(self, record):
- if "Form Dict" not in text_type(record.msg):
+ if "Form Dict" not in str(record.msg):
site = getattr(frappe.local, "site", None)
form_dict = getattr(frappe.local, "form_dict", None)
- record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, form_dict)
+ record.msg = str(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, form_dict)
return True
diff --git a/frappe/utils/make_random.py b/frappe/utils/make_random.py
index ad353dea84..2ebabb78f9 100644
--- a/frappe/utils/make_random.py
+++ b/frappe/utils/make_random.py
@@ -1,7 +1,5 @@
-from __future__ import unicode_literals
+
import frappe, random
-from six.moves import range
-from six import string_types
settings = frappe._dict(
prob = {
@@ -17,7 +15,7 @@ def add_random_children(doc, fieldname, rows, randomize, unique=None):
for i in range(nrows):
d = {}
for key, val in randomize.items():
- if isinstance(val[0], string_types):
+ if isinstance(val[0], str):
d[key] = get_random(*val)
else:
d[key] = random.randrange(*val)
diff --git a/frappe/utils/minify.py b/frappe/utils/minify.py
index 08d05c7225..b2cc93e554 100644
--- a/frappe/utils/minify.py
+++ b/frappe/utils/minify.py
@@ -1,4 +1,4 @@
-from __future__ import unicode_literals
+
# This code is original from jsmin by Douglas Crockford, it was translated to
# Python by Baruch Even. The original code had the following copyright and
# license.
@@ -29,7 +29,7 @@ from __future__ import unicode_literals
# SOFTWARE.
# */
-from six import StringIO
+from io import StringIO
def jsmin(js):
ins = StringIO(js)
diff --git a/frappe/utils/momentjs.py b/frappe/utils/momentjs.py
index 3c5ec91f63..18df9903a7 100644
--- a/frappe/utils/momentjs.py
+++ b/frappe/utils/momentjs.py
@@ -1,5 +1,4 @@
# get data for moment.js
-from __future__ import unicode_literals
def update(tz, out):
ltz = data["links"].get(tz, tz)
zone = data["zones"].get(ltz)
diff --git a/frappe/utils/nestedset.py b/frappe/utils/nestedset.py
index 531699db0c..3c024c40e4 100644
--- a/frappe/utils/nestedset.py
+++ b/frappe/utils/nestedset.py
@@ -10,8 +10,6 @@
# 3. call update_nsm(doc_obj) in the on_upate method
# ------------------------------------------
-from __future__ import unicode_literals
-
import frappe
from frappe import _
from frappe.model.document import Document
diff --git a/frappe/utils/oauth.py b/frappe/utils/oauth.py
index 6a92737a0d..9f5ff9d360 100644
--- a/frappe/utils/oauth.py
+++ b/frappe/utils/oauth.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
import frappe.utils
import json, jwt
import base64
from frappe import _
from frappe.utils.password import get_decrypted_password
-from six import string_types
class SignupDisabledError(frappe.PermissionError): pass
@@ -136,7 +134,7 @@ def get_info_via_oauth(provider, code, decoder=None, id_token=False):
token = parsed_access['id_token']
- info = jwt.decode(token, flow.client_secret, verify=False)
+ info = jwt.decode(token, flow.client_secret, options={"verify_signature": False})
else:
api_endpoint = oauth2_providers[provider].get("api_endpoint")
api_endpoint_args = oauth2_providers[provider].get("api_endpoint_args")
@@ -163,10 +161,10 @@ def login_oauth_user(data=None, provider=None, state=None, email_id=None, key=No
# return
# json.loads data and state
- if isinstance(data, string_types):
+ if isinstance(data, str):
data = json.loads(data)
- if isinstance(state, string_types):
+ if isinstance(state, str):
state = base64.b64decode(state)
state = json.loads(state.decode("utf-8"))
diff --git a/frappe/utils/password.py b/frappe/utils/password.py
index fbed3cd8e7..428f2e9577 100644
--- a/frappe/utils/password.py
+++ b/frappe/utils/password.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 string
import frappe
from frappe import _
@@ -61,7 +60,7 @@ def set_encrypted_password(doctype, name, pwd, fieldname='password'):
except frappe.db.DataError as e:
if ((frappe.db.db_type == 'mariadb' and e.args[0] == DATA_TOO_LONG) or
(frappe.db.db_type == 'postgres' and e.pgcode == STRING_DATA_RIGHT_TRUNCATION)):
- frappe.throw("Most probably your password is too long.", exc=e)
+ frappe.throw(_("Most probably your password is too long."), exc=e)
raise e
@@ -157,20 +156,29 @@ def create_auth_table():
frappe.db.create_auth_table()
-def encrypt(pwd):
- cipher_suite = Fernet(encode(get_encryption_key()))
- cipher_text = cstr(cipher_suite.encrypt(encode(pwd)))
+def encrypt(txt, encryption_key=None):
+ # Only use Fernet.generate_key().decode() to enter encyption_key value
+
+ try:
+ cipher_suite = Fernet(encode(encryption_key or get_encryption_key()))
+ except Exception:
+ # encryption_key is not in 32 url-safe base64-encoded format
+ frappe.throw(_('Encryption key is in invalid format!'))
+
+ cipher_text = cstr(cipher_suite.encrypt(encode(txt)))
return cipher_text
-def decrypt(pwd):
+def decrypt(txt, encryption_key=None):
+ # Only use encryption_key value generated with Fernet.generate_key().decode()
+
try:
- cipher_suite = Fernet(encode(get_encryption_key()))
- plain_text = cstr(cipher_suite.decrypt(encode(pwd)))
+ cipher_suite = Fernet(encode(encryption_key or get_encryption_key()))
+ plain_text = cstr(cipher_suite.decrypt(encode(txt)))
return plain_text
except InvalidToken:
# encryption_key in site_config is changed and not valid
- frappe.throw(_('Encryption key is invalid, Please check site_config.json'))
+ frappe.throw(_('Encryption key is invalid' + '!' if encryption_key else ', please check site_config.json.'))
def get_encryption_key():
diff --git a/frappe/utils/password_strength.py b/frappe/utils/password_strength.py
index a4182d1cab..3959d8c3dd 100644
--- a/frappe/utils/password_strength.py
+++ b/frappe/utils/password_strength.py
@@ -1,8 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
-
try:
from zxcvbn import zxcvbn
except Exception:
diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py
index 70464aafc5..fcf483bea6 100644
--- a/frappe/utils/pdf.py
+++ b/frappe/utils/pdf.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 io
import os
import re
@@ -9,7 +7,6 @@ from distutils.version import LooseVersion
import subprocess
import pdfkit
-import six
from bs4 import BeautifulSoup
from PyPDF2 import PdfFileReader, PdfFileWriter
@@ -45,6 +42,7 @@ def get_pdf(html, options=None, output=None):
except OSError as e:
if any([error in str(e) for error in PDF_CONTENT_ERRORS]):
if not filedata:
+ print(html, options)
frappe.throw(_("PDF generation failed because of broken image links"))
# allow pdfs with missing images if file got created
@@ -57,8 +55,6 @@ def get_pdf(html, options=None, output=None):
if "password" in options:
password = options["password"]
- if six.PY2:
- password = frappe.safe_encode(password)
if output:
output.appendPagesFromReader(reader)
diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py
index e83a5f6c71..40a393a2cc 100644
--- a/frappe/utils/print_format.py
+++ b/frappe/utils/print_format.py
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
import frappe, os
from frappe import _
@@ -9,7 +7,7 @@ from PyPDF2 import PdfFileWriter
no_cache = 1
-base_template_path = "templates/www/printview.html"
+base_template_path = "www/printview.html"
standard_format = "templates/print_formats/standard.html"
@frappe.whitelist()
diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py
index 678a61ca6e..8a7ff8334b 100644
--- a/frappe/utils/redis_wrapper.py
+++ b/frappe/utils/redis_wrapper.py
@@ -1,11 +1,12 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
+import pickle
+import re
-import redis, frappe, re
-from six.moves import cPickle as pickle
+import redis
+
+import frappe
from frappe.utils import cstr
-from six import iteritems
class RedisWrapper(redis.Redis):
@@ -165,8 +166,10 @@ class RedisWrapper(redis.Redis):
pass
def hgetall(self, name):
- return {key: pickle.loads(value) for key, value in
- iteritems(super(RedisWrapper, self).hgetall(self.make_key(name)))}
+ value = super(RedisWrapper, self).hgetall(self.make_key(name))
+ return {
+ key: pickle.loads(value) for key, value in value.items()
+ }
def hget(self, name, key, generator=None, shared=False):
_name = self.make_key(name, shared=shared)
diff --git a/frappe/utils/reset_doc.py b/frappe/utils/reset_doc.py
index 2119df5897..15aff4dc6c 100755
--- a/frappe/utils/reset_doc.py
+++ b/frappe/utils/reset_doc.py
@@ -1,4 +1,4 @@
-from __future__ import unicode_literals
+
import frappe
import json, os
from frappe.modules import scrub, get_module_path, utils
@@ -7,7 +7,7 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.core.page.permission_manager.permission_manager import get_standard_permissions
from frappe.permissions import setup_custom_perms
-from six.moves.urllib.request import urlopen
+from urllib.request import urlopen
branch = 'develop'
diff --git a/frappe/utils/response.py b/frappe/utils/response.py
index b152d69d8d..ca04f6def4 100644
--- a/frappe/utils/response.py
+++ b/frappe/utils/response.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 json
import datetime
import decimal
@@ -18,8 +17,7 @@ from werkzeug.wsgi import wrap_file
from werkzeug.wrappers import Response
from werkzeug.exceptions import NotFound, Forbidden
from frappe.utils import cint
-from six import text_type
-from six.moves.urllib.parse import quote
+from urllib.parse import quote
from frappe.core.doctype.access_log.access_log import make_access_log
@@ -125,13 +123,13 @@ def json_handler(obj):
import collections.abc
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
- return text_type(obj)
+ return str(obj)
elif isinstance(obj, decimal.Decimal):
return float(obj)
elif isinstance(obj, LocalProxy):
- return text_type(obj)
+ return str(obj)
elif isinstance(obj, frappe.model.document.BaseDocument):
doc = obj.as_dict(no_nulls=True)
@@ -149,8 +147,8 @@ def json_handler(obj):
def as_page():
"""print web page"""
- from frappe.website.render import render
- return render(frappe.response['route'], http_status_code=frappe.response.get("http_status_code"))
+ from frappe.website.serve import get_response
+ return get_response(frappe.response['route'], http_status_code=frappe.response.get("http_status_code"))
def redirect():
return werkzeug.utils.redirect(frappe.response.location)
@@ -217,7 +215,8 @@ def send_private_file(path):
return response
def handle_session_stopped():
+ from frappe.website.serve import get_response
frappe.respond_as_web_page(_("Updating"),
_("Your system is being updated. Please refresh again after a few moments."),
http_status_code=503, indicator_color='orange', fullpage = True, primary_action=None)
- return frappe.website.render.render("message", http_status_code=503)
+ return get_response("message", http_status_code=503)
diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py
index 643812b226..2e27859faa 100644
--- a/frappe/utils/safe_exec.py
+++ b/frappe/utils/safe_exec.py
@@ -5,6 +5,7 @@ from html2text import html2text
from RestrictedPython import compile_restricted, safe_globals
import RestrictedPython.Guards
import frappe
+from frappe import _
import frappe.utils
import frappe.utils.data
from frappe.website.utils import (get_shade, get_toc, get_next_link)
@@ -31,7 +32,7 @@ class NamespaceDict(frappe._dict):
def safe_exec(script, _globals=None, _locals=None):
# script reports must be enabled via site_config.json
if not frappe.conf.server_script_enabled:
- frappe.throw('Please Enable Server Scripts', ServerScriptNotEnabled)
+ frappe.throw(_('Please Enable Server Scripts'), ServerScriptNotEnabled)
# build globals
exec_globals = get_safe_globals()
@@ -61,7 +62,9 @@ def get_safe_globals():
out = NamespaceDict(
# make available limited methods of frappe
- json=json,
+ json=NamespaceDict(
+ loads = json.loads,
+ dumps = json.dumps),
dict=dict,
log=frappe.log,
_dict=frappe._dict,
@@ -82,6 +85,7 @@ def get_safe_globals():
get_list=frappe.get_list,
get_all=frappe.get_all,
get_system_settings=frappe.get_system_settings,
+ rename_doc=frappe.rename_doc,
utils=datautils,
get_url=frappe.utils.get_url,
@@ -148,6 +152,7 @@ def get_safe_globals():
# default writer allows write access
out._write_ = _write
out._getitem_ = _getitem
+ out._getattr_ = _getattr
# allow iterators and list comprehension
out._getiter_ = iter
@@ -174,6 +179,27 @@ def _getitem(obj, key):
raise SyntaxError('Key starts with _')
return obj[key]
+def _getattr(object, name, default=None):
+ # guard function for RestrictedPython
+ # allow any key to be accessed as long as
+ # 1. it does not start with an underscore (safer_getattr)
+ # 2. it is not an UNSAFE_ATTRIBUTES
+
+ 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",
+ }
+
+ if isinstance(name, str) and (name in UNSAFE_ATTRIBUTES):
+ raise SyntaxError("{name} is an unsafe attribute".format(name=name))
+ return RestrictedPython.Guards.safer_getattr(object, name, default=default)
+
def _write(obj):
# guard function for RestrictedPython
# allow writing to any object
diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py
index 749a41682f..0bfc50eab5 100755
--- a/frappe/utils/scheduler.py
+++ b/frappe/utils/scheduler.py
@@ -7,8 +7,6 @@ Events:
monthly
weekly
"""
-# imports - compatibility imports
-from __future__ import print_function, unicode_literals
# imports - standard imports
import os
diff --git a/frappe/utils/testutils.py b/frappe/utils/testutils.py
index 4af3ad647c..c451d090f1 100644
--- a/frappe/utils/testutils.py
+++ b/frappe/utils/testutils.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
def add_custom_field(doctype, fieldname, fieldtype='Data', options=None):
diff --git a/frappe/utils/user.py b/frappe/utils/user.py
index 61b698db9f..fe768c28c5 100755
--- a/frappe/utils/user.py
+++ b/frappe/utils/user.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
from frappe import _dict
import frappe.share
diff --git a/frappe/utils/verified_command.py b/frappe/utils/verified_command.py
index 971c0a243c..4b038b88c3 100644
--- a/frappe/utils/verified_command.py
+++ b/frappe/utils/verified_command.py
@@ -1,20 +1,17 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-
-from __future__ import unicode_literals
import hmac, hashlib
-from six.moves.urllib.parse import urlencode
+from urllib.parse import urlencode
from frappe import _
import frappe
import frappe.utils
-from six import string_types
def get_signed_params(params):
"""Sign a url by appending `&_signature=xxxxx` to given params (string or dict).
:param params: String or dict of parameters."""
- if not isinstance(params, string_types):
+ if not isinstance(params, str):
params = urlencode(params)
signature = hmac.new(params.encode(), digestmod=hashlib.md5)
diff --git a/frappe/website/context.py b/frappe/website/context.py
deleted file mode 100644
index c898d39869..0000000000
--- a/frappe/website/context.py
+++ /dev/null
@@ -1,297 +0,0 @@
-# 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.website.doctype.website_settings.website_settings import get_website_settings
-from frappe.website.router import get_page_context
-from frappe.model.document import Document
-
-def get_context(path, args=None):
- if args and args.source:
- context = args
- else:
- context = get_page_context(path)
- if args:
- context.update(args)
-
- if hasattr(frappe.local, 'request'):
- # for (remove leading slash)
- # path could be overriden in render.resolve_from_map
- context["path"] = frappe.local.request.path.strip('/ ')
- else:
- context["path"] = path
-
- context.canonical = frappe.utils.get_url(frappe.utils.escape_html(context.path))
- context.route = context.path
- context = build_context(context)
-
- # set using frappe.respond_as_web_page
- if hasattr(frappe.local, 'response') and frappe.local.response.get('context'):
- context.update(frappe.local.response.context)
-
- # to be able to inspect the context dict
- # Use the macro "inspect" from macros.html
- context._context_dict = context
-
- context.developer_mode = frappe.conf.developer_mode
-
- return context
-
-def update_controller_context(context, controller):
- module = frappe.get_module(controller)
-
- if module:
- # get config fields
- for prop in ("base_template_path", "template", "no_cache", "sitemap",
- "condition_field"):
- if hasattr(module, prop):
- context[prop] = getattr(module, prop)
-
- if hasattr(module, "get_context"):
- import inspect
- try:
- if inspect.getfullargspec(module.get_context).args:
- ret = module.get_context(context)
- else:
- ret = module.get_context()
- if ret:
- context.update(ret)
- except (frappe.PermissionError, frappe.PageDoesNotExistError, frappe.Redirect):
- raise
- except:
- if not any([frappe.flags.in_migrate, frappe.flags.in_website_search_build]):
- frappe.errprint(frappe.utils.get_traceback())
-
- if hasattr(module, "get_children"):
- context.children = module.get_children(context)
-
-
-def build_context(context):
- """get_context method of doc or module is supposed to render
- content templates and push it into context"""
- context = frappe._dict(context)
-
- if not "url_prefix" in context:
- context.url_prefix = ""
-
- if context.url_prefix and context.url_prefix[-1]!='/':
- context.url_prefix += '/'
-
- # for backward compatibility
- context.docs_base_url = '/docs'
-
- context.update(get_website_settings(context))
- context.update(frappe.local.conf.get("website_context") or {})
-
- # provide doc
- if context.doc:
- context.update(context.doc.as_dict())
- context.update(context.doc.get_website_properties())
-
- if not context.template:
- context.template = context.doc.meta.get_web_template()
-
- if hasattr(context.doc, "get_context"):
- ret = context.doc.get_context(context)
-
- if ret:
- context.update(ret)
-
- for prop in ("no_cache", "sitemap"):
- if not prop in context:
- context[prop] = getattr(context.doc, prop, False)
-
- elif context.controller:
- # controller based context
- update_controller_context(context, context.controller)
-
- # controller context extensions
- context_controller_hooks = frappe.get_hooks("extend_website_page_controller_context") or {}
- for controller, extension in context_controller_hooks.items():
- if isinstance(extension, list):
- for ext in extension:
- if controller == context.controller:
- update_controller_context(context, ext)
- else:
- update_controller_context(context, extension)
-
- add_metatags(context)
- add_sidebar_and_breadcrumbs(context)
-
- # determine templates to be used
- if not context.base_template_path:
- app_base = frappe.get_hooks("base_template")
- context.base_template_path = app_base[-1] if app_base else "templates/base.html"
-
- if context.title_prefix and context.title and not context.title.startswith(context.title_prefix):
- context.title = '{0} - {1}'.format(context.title_prefix, context.title)
-
- # apply context from hooks
- update_website_context = frappe.get_hooks('update_website_context')
- for method in update_website_context:
- values = frappe.get_attr(method)(context)
- if values:
- context.update(values)
-
- return context
-
-def load_sidebar(context, sidebar_json_path):
- with open(sidebar_json_path, 'r') as sidebarfile:
- try:
- sidebar_json = sidebarfile.read()
- context.sidebar_items = json.loads(sidebar_json)
- context.show_sidebar = 1
- except json.decoder.JSONDecodeError:
- frappe.throw('Invalid Sidebar JSON at ' + sidebar_json_path)
-
-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 add_sidebar_and_breadcrumbs(context):
- '''Add sidebar and breadcrumbs to context'''
- from frappe.website.router import get_page_info_from_template
- if context.show_sidebar:
- context.no_cache = 1
- add_sidebar_data(context)
- else:
- if context.basepath:
- hooks = frappe.get_hooks('look_for_sidebar_json')
- look_for_sidebar_json = hooks[0] if hooks else 0
- sidebar_json_path = get_sidebar_json_path(
- context.basepath,
- look_for_sidebar_json
- )
- if sidebar_json_path:
- load_sidebar(context, sidebar_json_path)
-
- if context.add_breadcrumbs and not context.parents:
- if context.basepath:
- parent_path = os.path.dirname(context.path).rstrip('/')
- page_info = get_page_info_from_template(parent_path)
- if page_info:
- context.parents = [dict(route=parent_path, title=page_info.title)]
-
-def add_sidebar_data(context):
- from frappe.utils.user import get_fullname_and_avatar
- import frappe.www.list
-
- if context.show_sidebar and context.website_sidebar:
- context.sidebar_items = frappe.get_all('Website Sidebar Item',
- filters=dict(parent=context.website_sidebar), fields=['title', 'route', '`group`'],
- order_by='idx asc')
-
- if not context.sidebar_items:
- sidebar_items = frappe.cache().hget('portal_menu_items', frappe.session.user)
- if sidebar_items == 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)
-
- context.sidebar_items = sidebar_items
-
- info = get_fullname_and_avatar(frappe.session.user)
- context["fullname"] = info.fullname
- context["user_image"] = info.avatar
- context["user"] = info.name
-
-
-def add_metatags(context):
- tags = frappe._dict(context.get("metatags") or {})
-
- if "og:type" not in tags:
- tags["og:type"] = "article"
-
- if "title" not in tags and context.title:
- tags["title"] = context.title
-
- title = tags.get("name") or tags.get("title")
- if title:
- tags["og:title"] = tags["twitter:title"] = title
- tags["twitter:card"] = "summary"
-
- if "description" not in tags and context.description:
- tags["description"] = context.description
-
- description = tags.get("description")
- if description:
- tags["og:description"] = tags["twitter:description"] = description
-
- if "image" not in tags and context.image:
- tags["image"] = context.image
-
- image = tags.get("image")
- if image:
- tags["og:image"] = tags["twitter:image"] = tags["image"] = frappe.utils.get_url(image)
- tags['twitter:card'] = "summary_large_image"
-
- if "author" not in tags and context.author:
- tags["author"] = context.author
-
- tags["og:url"] = tags["url"] = frappe.utils.get_url(context.path)
-
- if "published_on" not in tags and context.published_on:
- tags["published_on"] = context.published_on
-
- if "published_on" in tags:
- tags["datePublished"] = tags["published_on"]
- del tags["published_on"]
-
- tags["language"] = frappe.local.lang or "en"
-
- # Get meta tags from Website Route meta
- # they can override the defaults set above
- route = context.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()
- tags.update(d)
-
- # update tags in context
- context.metatags = tags
diff --git a/frappe/website/doctype/__init__.py b/frappe/website/doctype/__init__.py
index baffc48825..8b13789179 100644
--- a/frappe/website/doctype/__init__.py
+++ b/frappe/website/doctype/__init__.py
@@ -1 +1 @@
-from __future__ import unicode_literals
+
diff --git a/frappe/website/doctype/about_us_settings/about_us_settings.py b/frappe/website/doctype/about_us_settings/about_us_settings.py
index 5b93cdcede..1d45adeb42 100644
--- a/frappe/website/doctype/about_us_settings/about_us_settings.py
+++ b/frappe/website/doctype/about_us_settings/about_us_settings.py
@@ -3,17 +3,16 @@
# For license information, please see license.txt
-from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class AboutUsSettings(Document):
-
+
def on_update(self):
- from frappe.website.render import clear_cache
+ from frappe.website.utils import clear_cache
clear_cache("about")
-
+
def get_args():
obj = frappe.get_doc("About Us Settings")
return {
diff --git a/frappe/website/doctype/about_us_settings/test_about_us_settings.py b/frappe/website/doctype/about_us_settings/test_about_us_settings.py
index 85173a20e5..ab47505fb1 100644
--- a/frappe/website/doctype/about_us_settings/test_about_us_settings.py
+++ b/frappe/website/doctype/about_us_settings/test_about_us_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/website/doctype/about_us_team_member/about_us_team_member.py b/frappe/website/doctype/about_us_team_member/about_us_team_member.py
index a872a6e5e2..864b6a3e81 100644
--- a/frappe/website/doctype/about_us_team_member/about_us_team_member.py
+++ b/frappe/website/doctype/about_us_team_member/about_us_team_member.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/website/doctype/blog_category/blog_category.py b/frappe/website/doctype/blog_category/blog_category.py
index 375ba5b6a3..d7eb92ca7a 100644
--- a/frappe/website/doctype/blog_category/blog_category.py
+++ b/frappe/website/doctype/blog_category/blog_category.py
@@ -1,9 +1,8 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
-from __future__ import unicode_literals
from frappe.website.website_generator import WebsiteGenerator
-from frappe.website.render import clear_cache
+from frappe.website.utils import clear_cache
class BlogCategory(WebsiteGenerator):
def autoname(self):
diff --git a/frappe/website/doctype/blog_category/test_blog_category.py b/frappe/website/doctype/blog_category/test_blog_category.py
index fe8f4544cd..d9093e76f0 100644
--- a/frappe/website/doctype/blog_category/test_blog_category.py
+++ b/frappe/website/doctype/blog_category/test_blog_category.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/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json
index 909cecf867..c2491ee5a4 100644
--- a/frappe/website/doctype/blog_post/blog_post.json
+++ b/frappe/website/doctype/blog_post/blog_post.json
@@ -18,6 +18,7 @@
"featured",
"hide_cta",
"disable_comments",
+ "disable_feedback",
"section_break_5",
"blog_intro",
"content_type",
@@ -191,6 +192,13 @@
"fieldtype": "Data",
"label": "Meta Title",
"length": 60
+ },
+ {
+ "default": "0",
+ "description": "Feedback on this blog post will be disabled if checked.",
+ "fieldname": "disable_feedback",
+ "fieldtype": "Check",
+ "label": "Disable Feedback"
}
],
"has_web_view": 1,
@@ -200,7 +208,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 5,
- "modified": "2020-12-23 14:28:36.311389",
+ "modified": "2021-06-14 13:50:02.109719",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py
index bfccc0bbc7..965fc8e3e0 100644
--- a/frappe/website/doctype/blog_post/blog_post.py
+++ b/frappe/website/doctype/blog_post/blog_post.py
@@ -1,23 +1,16 @@
# 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.website.website_generator import WebsiteGenerator
-from frappe.website.render import clear_cache
+from frappe.website.utils import clear_cache
from frappe.utils import today, cint, global_date_format, get_fullname, strip_html_tags, markdown, sanitize_html
from math import ceil
from frappe.website.utils import (find_first_image, get_html_content_based_on_type,
get_comment_list)
class BlogPost(WebsiteGenerator):
- website = frappe._dict(
- route = 'blog',
- order_by = "published_on desc"
- )
-
@frappe.whitelist()
def make_route(self):
if not self.route:
@@ -104,12 +97,14 @@ class BlogPost(WebsiteGenerator):
context.metatags["image"] = self.meta_image or image or None
self.load_comments(context)
+ self.load_feedback(context)
context.category = frappe.db.get_value("Blog Category",
context.doc.blog_category, ["title", "route"], as_dict=1)
context.parents = [{"name": _("Home"), "route":"/"},
{"name": "Blog", "route": "/blog"},
{"label": context.category.title, "route":context.category.route}]
+ context.guest_allowed = True
def fetch_cta(self):
if frappe.db.get_single_value("Blog Settings", "show_cta_in_blog", cache=True):
@@ -151,6 +146,17 @@ class BlogPost(WebsiteGenerator):
else:
context.comment_text = _('{0} comments').format(len(context.comment_list))
+ def load_feedback(self, context):
+ feedback = frappe.get_all('Feedback',
+ fields=['email', 'feedback', 'rating'],
+ filters=dict(
+ reference_doctype=self.doctype,
+ reference_name=self.name,
+ email=frappe.session.user
+ )
+ )
+ context.user_feedback = feedback[0] if feedback else ''
+
def set_read_time(self):
content = self.content or self.content_html or ''
if self.content_type == "Markdown":
diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html
index dad8b97164..4678622062 100644
--- a/frappe/website/doctype/blog_post/templates/blog_post.html
+++ b/frappe/website/doctype/blog_post/templates/blog_post.html
@@ -65,6 +65,11 @@
{% include 'templates/includes/comments/comments.html' %}
{{ 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 -%}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 @@ + + + + + + +