diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 5a91016e32..738fb73a34 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -87,10 +87,6 @@ class DocType(Document): if self.default_print_format and not self.custom: frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) - if frappe.conf.get('developer_mode'): - self.owner = 'Administrator' - self.modified_by = 'Administrator' - def validate_field_name_conflicts(self): """Check if field names dont conflict with controller properties and methods""" core_doctypes = [ @@ -177,7 +173,6 @@ class DocType(Document): if self.is_virtual and self.custom: frappe.throw(_("Not allowed to create custom Virtual DocType."), CannotCreateStandardDoctypeError) - if frappe.conf.get('developer_mode'): self.owner = 'Administrator' self.modified_by = 'Administrator' @@ -315,9 +310,7 @@ class DocType(Document): if allow_doctype_export: self.export_doc() self.make_controller_template() - - if self.has_web_view: - self.set_base_class_for_controller() + self.set_base_class_for_controller() # update index if not self.custom: @@ -355,23 +348,49 @@ class DocType(Document): now=now, doctype=self.name) def set_base_class_for_controller(self): - '''Updates the controller class to subclass from `WebsiteGenertor`, - if it is a subclass of `Document`''' - controller_path = frappe.get_module_path(frappe.scrub(self.module), - 'doctype', frappe.scrub(self.name), frappe.scrub(self.name) + '.py') + """If DocType.has_web_view has been changed, updates the controller class and import + from `WebsiteGenertor` to `Document` or viceversa""" - with open(controller_path, 'r') as f: + if not self.has_value_changed("has_web_view"): + return + + despaced_name = self.name.replace(" ", "_") + scrubbed_name = frappe.scrub(self.name) + scrubbed_module = frappe.scrub(self.module) + controller_path = frappe.get_module_path( + scrubbed_module, "doctype", scrubbed_name, f"{scrubbed_name}.py" + ) + + document_cls_tag = f"class {despaced_name}(Document)" + document_import_tag = "from frappe.model.document import Document" + website_generator_cls_tag = f"class {despaced_name}(WebsiteGenerator)" + website_generator_import_tag = "from frappe.website.generators.website_generator import WebsiteGenerator" + + with open(controller_path) as f: code = f.read() + updated_code = code - class_string = '\nclass {0}(Document)'.format(self.name.replace(' ', '')) - if '\nfrom frappe.model.document import Document' in code and class_string in code: - code = code.replace('from frappe.model.document import Document', - 'from frappe.website.website_generator import WebsiteGenerator') - code = code.replace('class {0}(Document)'.format(self.name.replace(' ', '')), - 'class {0}(WebsiteGenerator)'.format(self.name.replace(' ', ''))) + is_website_generator_class = all([ + website_generator_cls_tag in code, + website_generator_import_tag in code + ]) - with open(controller_path, 'w') as f: - f.write(code) + if self.has_web_view and not is_website_generator_class: + updated_code = updated_code.replace( + document_import_tag, website_generator_import_tag + ).replace( + document_cls_tag, website_generator_cls_tag + ) + elif not self.has_web_view and is_website_generator_class: + updated_code = updated_code.replace( + website_generator_import_tag, document_import_tag + ).replace( + website_generator_cls_tag, document_cls_tag + ) + + if updated_code != code: + with open(controller_path, "w") as f: + f.write(updated_code) def run_module_method(self, method): from frappe.modules import load_doctype_module diff --git a/frappe/database/database.py b/frappe/database/database.py index df5ad6dfda..c0d377fd42 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -170,6 +170,12 @@ class Database(object): frappe.errprint('Syntax error in query:') frappe.errprint(query) + elif self.is_deadlocked(e): + raise frappe.QueryDeadlockError + + elif self.is_timedout(e): + raise frappe.QueryTimeoutError + if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)): pass else: diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 4b59f8f38f..8449425bc1 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -101,6 +101,8 @@ class DataTooLongException(ValidationError): pass class FileAlreadyAttachedException(Exception): pass class DocumentAlreadyRestored(ValidationError): pass class AttachmentLimitReached(ValidationError): pass +class QueryTimeoutError(Exception): pass +class QueryDeadlockError(Exception): pass # OAuth exceptions class InvalidAuthorizationHeader(CSRFTokenError): pass class InvalidAuthorizationPrefix(CSRFTokenError): pass diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py index 17e84ee488..ab6ffd4985 100644 --- a/frappe/modules/export_file.py +++ b/frappe/modules/export_file.py @@ -24,7 +24,7 @@ def write_document_file(doc, record_module=None, create_init=True, folder_name=N doc_export = doc.as_dict(no_nulls=True) doc.run_method("before_export", doc_export) - strip_default_fields(doc, doc_export) + doc_export = strip_default_fields(doc, doc_export) module = record_module or get_module_name(doc) # create folder @@ -42,12 +42,17 @@ def write_document_file(doc, record_module=None, create_init=True, folder_name=N def strip_default_fields(doc, doc_export): # strip out default fields from children + if doc.doctype == "DocType" and doc.migration_hash: + del doc_export["migration_hash"] + for df in doc.meta.get_table_fields(): for d in doc_export.get(df.fieldname): for fieldname in frappe.model.default_fields: if fieldname in d: del d[fieldname] + return doc_export + def write_code_files(folder, fname, doc, doc_export): '''Export code files and strip from values''' if hasattr(doc, 'get_code_fields'): @@ -59,8 +64,6 @@ def write_code_files(folder, fname, doc, doc_export): # remove from exporting del doc_export[key] - - def get_module_name(doc): if doc.doctype == 'Module Def': module = doc.name diff --git a/frappe/patches/v11_0/remove_skip_for_doctype.py b/frappe/patches/v11_0/remove_skip_for_doctype.py index 1063dca3ff..1bbe74bb6d 100644 --- a/frappe/patches/v11_0/remove_skip_for_doctype.py +++ b/frappe/patches/v11_0/remove_skip_for_doctype.py @@ -75,7 +75,7 @@ def execute(): if new_user_permissions_list: frappe.qb.into("User Permission").columns( "name", "user", "allow", "for_value", "applicable_for", "apply_to_all_doctypes", "creation", "modified" - ).insert(tuple(new_user_permissions_list)).run() + ).insert(*new_user_permissions_list).run() if user_permissions_to_delete: frappe.db.delete( diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index de174cf37f..311a5b7a1e 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -320,7 +320,7 @@ export default class GridRow { @@ -397,7 +397,7 @@ export default class GridRow { ${frappe.utils.icon('drag', 'xs')}
- ${docfield.label} + ${__(docfield.label)}
CustomError + var exception = data.exception.split('.').at(-1); + var exception_handler = exception_handlers[exception]; + if (exception_handler) { + exception_handler(data); + return; + } + } + } var status_code_handler = statusCode[xhr.statusCode().status]; if (status_code_handler) { status_code_handler(xhr); - } else { - // if not handled by error handler! - opts.error_callback && opts.error_callback(xhr); + return; } + // if not handled by error handler! + opts.error_callback && opts.error_callback(xhr); } catch(e) { console.log("Unable to handle failed response"); // eslint-disable-line console.trace(e); // eslint-disable-line diff --git a/frappe/public/scss/login.bundle.scss b/frappe/public/scss/login.bundle.scss index 17f33b0a67..3963fbecc6 100644 --- a/frappe/public/scss/login.bundle.scss +++ b/frappe/public/scss/login.bundle.scss @@ -135,7 +135,7 @@ body { } .social-logins { - margin: var(--margin-md) 0; + margin-top: var(--margin-md); font-size: var(--text-md); .social-login-buttons { @@ -147,7 +147,11 @@ body { } min-width: 50%; padding: 0 4px; - margin-bottom: var(--margin-sm); + margin-bottom: var(--margin-md); + + &:last-child { + margin-bottom: 0; + } } } } diff --git a/frappe/test_runner.py b/frappe/test_runner.py index ecacaa1a89..b6a8145cfb 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -3,7 +3,6 @@ import frappe import unittest, json, sys, os import time -import xmlrunner import importlib from frappe.modules import load_doctype_module, get_module_name import frappe.utils.scheduler @@ -17,6 +16,13 @@ SLOW_TEST_THRESHOLD = 2 def xmlrunner_wrapper(output): """Convenience wrapper to keep method signature unchanged for XMLTestRunner and TextTestRunner""" + try: + import xmlrunner + except ImportError: + print("Development dependencies are required to execute this command. To install run:") + print("$ bench setup requirements --dev") + raise + def _runner(*args, **kwargs): kwargs['output'] = output return xmlrunner.XMLTestRunner(*args, **kwargs) diff --git a/frappe/translate.py b/frappe/translate.py index e5c1c9ef10..03720f115d 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -20,7 +20,7 @@ from typing import List, Union, Tuple import frappe from frappe.model.utils import InvalidIncludePath, render_include from frappe.utils import get_bench_path, is_html, strip, strip_html_tags -from frappe.query_builder import Field +from frappe.query_builder import Field, DocType from pypika.terms import PseudoColumn @@ -334,14 +334,15 @@ def clear_cache(): def get_messages_for_app(app, deduplicate=True): """Returns all messages (list) for a specified `app`""" messages = [] - modules = ", ".join('"{}"'.format(m.title().replace("_", " ")) \ - for m in frappe.local.app_modules[app]) + modules = [frappe.unscrub(m) for m in frappe.local.app_modules[app]] # doctypes if modules: + if isinstance(modules, str): + modules = [modules] filtered_doctypes = frappe.qb.from_("DocType").where( Field("module").isin(modules) - ).select("name").run() + ).select("name").run(pluck=True) for name in filtered_doctypes: messages.extend(get_messages_from_doctype(name)) @@ -355,9 +356,14 @@ def get_messages_for_app(app, deduplicate=True): # reports - for name in frappe.db.sql_list("""select tabReport.name from tabDocType, tabReport - where tabReport.ref_doctype = tabDocType.name - and tabDocType.module in ({})""".format(modules)): + report = DocType("Report") + doctype = DocType("DocType") + names = ( + frappe.qb.from_(doctype) + .from_(report) + .where((report.ref_doctype == doctype.name) & doctype.module.isin(modules)) + .select(report.name).run(pluck=True)) + for name in names: messages.append((None, name)) messages.extend(get_messages_from_report(name)) for i in messages: diff --git a/frappe/utils/response.py b/frappe/utils/response.py index d7119ebe3b..3dc6fa0c80 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -26,7 +26,10 @@ def report_error(status_code): allow_traceback = cint(frappe.db.get_system_setting('allow_error_traceback')) if frappe.db else True if (allow_traceback and (status_code!=404 or frappe.conf.logging) and not frappe.local.flags.disable_traceback): - frappe.errprint(frappe.utils.get_traceback()) + traceback = frappe.utils.get_traceback() + if traceback: + frappe.errprint(traceback) + frappe.local.response.exception = traceback.splitlines()[-1] response = build_response("json") response.status_code = status_code diff --git a/frappe/utils/weasyprint.py b/frappe/utils/weasyprint.py index 006bab2dd0..44770f7412 100644 --- a/frappe/utils/weasyprint.py +++ b/frappe/utils/weasyprint.py @@ -1,8 +1,20 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See LICENSE +import click + import frappe -from weasyprint import HTML, CSS + +try: + from weasyprint import HTML, CSS +except OSError: + click.secho( + "\n".join(["WeasyPrint depdends on additional system dependencies.", + "Follow instructions specific to your operating system:", + "https://doc.courtbouillon.org/weasyprint/stable/first_steps.html"]), + fg="yellow" + ) + raise @frappe.whitelist() diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 869c82b917..cb8008277c 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -166,6 +166,8 @@ def abs_url(path): return if path.startswith('http://') or path.startswith('https://'): return path + if path.startswith('tel:'): + return path if path.startswith('data:'): return path if not path.startswith("/"): diff --git a/frappe/www/login.html b/frappe/www/login.html index 4c0c54d76f..85e5ae9296 100644 --- a/frappe/www/login.html +++ b/frappe/www/login.html @@ -79,7 +79,11 @@
{%- if social_login -%}
+ + {{ email_login_body() }} + -
{% else %} {{ email_login_body() }}