Merge branch 'frappe:develop' into multiple_imap_folder

This commit is contained in:
Manuel 2021-10-27 09:14:49 +02:00 committed by GitHub
commit ce488e6a6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 140 additions and 46 deletions

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -320,7 +320,7 @@ export default class GridRow {
</div>
<p class='help-box small text-muted hidden-xs'>
<a class='add-new-fields text-muted'>
+ Add / Remove Columns
+ ${__('Add / Remove Columns')}
</a>
</p>
</div>
@ -397,7 +397,7 @@ export default class GridRow {
<a style='cursor: grabbing;'>${frappe.utils.icon('drag', 'xs')}</a>
</div>
<div class='col-md-7' style='padding-left:0px;'>
${docfield.label}
${__(docfield.label)}
</div>
<div class='col-md-3' style='padding-left:0px;margin-top:-2px;' title='${__('Columns')}'>
<input class='form-control column-width input-xs text-right'

View file

@ -206,6 +206,25 @@ frappe.request.call = function(opts) {
}
};
var exception_handlers = {
'QueryTimeoutError': function() {
frappe.utils.play_sound("error");
frappe.msgprint({
title: __('Request Timeout'),
indicator: 'red',
message: __("Server was too busy to process this request. Please try again.")
});
},
'QueryDeadlockError': function() {
frappe.utils.play_sound("error");
frappe.msgprint({
title: __('Deadlock Occurred'),
indicator: 'red',
message: __("Server was too busy to process this request. Please try again.")
});
}
};
var ajax_args = {
url: opts.url || frappe.request.url,
data: opts.args,
@ -272,13 +291,25 @@ frappe.request.call = function(opts) {
})
.fail(function(xhr, textStatus) {
try {
if (xhr.responseText) {
var data = JSON.parse(xhr.responseText);
if (data.exception) {
// frappe.exceptions.CustomError -> 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

View file

@ -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;
}
}
}
}

View file

@ -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)

View file

@ -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:

View file

@ -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

View file

@ -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()

View file

@ -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("/"):

View file

@ -79,7 +79,11 @@
<form class="form-signin form-login" role="form">
{%- if social_login -%}
<div class="page-card-body">
<form class="form-signin form-login" role="form">
{{ email_login_body() }}
</form>
<div class="social-logins text-center">
<p class="text-muted login-divider">{{ _("or") }}</p>
<div class="social-login-buttons">
{% for provider in provider_logins %}
<div class="login-button-wrapper">
@ -91,12 +95,8 @@
{{ _("Login With {0}").format(provider.provider_name) }}</a>
</div>
{% endfor %}
<p class="text-muted login-divider">{{ _("or") }}</p>
</div>
</div>
<a href="#email"
class="btn btn-block btn-default btn-sm btn-login-option">
{{ _("Login With Email") }}</a>
</div>
{% else %}
{{ email_login_body() }}