Merge branch 'frappe:develop' into multiple_imap_folder
This commit is contained in:
commit
ce488e6a6f
14 changed files with 140 additions and 46 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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("/"):
|
||||
|
|
|
|||
|
|
@ -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() }}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue