Merge branch 'develop' into restore-fix

This commit is contained in:
Suraj Shetty 2020-06-01 11:54:52 +05:30 committed by GitHub
commit 7aaf15a804
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 669 additions and 255 deletions

View file

@ -25,6 +25,7 @@ cache:
# https://docs.cypress.io/guides/guides/continuous-integration.html#Caching
- ~/.cache
matrix:
include:
- name: "Python 3.7 MariaDB"
@ -46,7 +47,26 @@ matrix:
script: bench --site test_site run-ui-tests frappe --headless
before_install:
# install wkhtmltopdf
# do we really want to run travis?
- |
ONLY_DOCS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.(md|png|jpg|jpeg)$|^.github|LICENSE' ; echo $?)
ONLY_JS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.js$' ; echo $?)
ONLY_PY_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.py$' ; echo $?)
if [[ $ONLY_DOCS_CHANGES == "1" ]]; then
echo "Only docs were updated, stopping build process.";
exit;
fi
if [[ $ONLY_JS_CHANGES == "1" && $TYPE == "server" ]]; then
echo "Only JavaScript code was updated; Stopping Python build process.";
exit;
fi
if [[ $ONLY_PY_CHANGES == "1" && $TYPE == "ui" ]]; then
echo "Only Python code was updated, stopping Cypress build process.";
exit;
fi
# install wkhtmltopdf
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf

View file

@ -1559,10 +1559,10 @@ def get_doctype_app(doctype):
loggers = {}
log_level = None
def logger(module=None, with_more_info=True):
def logger(module=None, with_more_info=False):
'''Returns a python logger that uses StreamHandler'''
from frappe.utils.logger import get_logger
return get_logger(module or 'default', with_more_info=with_more_info)
return get_logger(module=module, with_more_info=with_more_info)
def log_error(message=None, title=_("Error")):
'''Log error to Error Log'''

View file

@ -99,6 +99,16 @@ def application(request):
frappe.monitor.stop(response)
frappe.recorder.dump()
frappe.logger("web").info({
"site": get_site_name(request.host),
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"),
"base_url": getattr(request, "base_url", "NOTFOUND"),
"full_path": getattr(request, "full_path", "NOTFOUND"),
"method": getattr(request, "method", "NOTFOUND"),
"scheme": getattr(request, "scheme", "NOTFOUND"),
"http_status_code": getattr(response, "status_code", "NOTFOUND")
})
if response and hasattr(frappe.local, 'rate_limiter'):
response.headers.extend(frappe.local.rate_limiter.headers())
@ -195,7 +205,6 @@ def handle_exception(e):
frappe.local.login_manager.clear_cookies()
if http_status_code >= 500:
frappe.logger().error('Request Error', exc_info=True)
make_error_snapshot(e)
if return_as_message:

View file

@ -19,6 +19,7 @@ from frappe.email.inbox import get_email_accounts
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
from frappe.model.base_document import get_controller
from frappe.social.doctype.post.post import frequently_visited_links
def get_bootinfo():
@ -106,6 +107,7 @@ def load_desktop_data(bootinfo):
from frappe.desk.desktop import get_desk_sidebar_items
bootinfo.allowed_modules = get_modules_from_all_apps_for_user()
bootinfo.allowed_workspaces = get_desk_sidebar_items(True)
bootinfo.module_page_map = get_controller("Desk Page").get_module_page_map()
bootinfo.dashboards = frappe.get_all("Dashboard")
def get_allowed_pages(cache=False):

View file

@ -447,7 +447,7 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path=
else:
click.echo("="*80)
click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site))
click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n"))
click.echo("Reason: {reason}{sep}".format(reason=str(err), sep="\n"))
click.echo("Fix the issue and try again.")
click.echo(
"Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site)

View file

@ -158,11 +158,7 @@ class TestAccessLog(unittest.TestCase):
request = requests.post(private_file_link, headers=self.header)
last_doc = frappe.get_last_doc('Access Log')
if request.status_code == 403:
# if file is not accessible, access log wont be generated
pass
else:
if request.ok:
# check for the access log of downloaded file
self.assertEqual(new_private_file.doctype, last_doc.export_from)
self.assertEqual(new_private_file.name, last_doc.reference_document)

View file

@ -259,7 +259,12 @@ class Communication(Document):
# Timeline Links
def set_timeline_links(self):
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
contacts = []
if (self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact")) or \
frappe.flags.in_test:
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
for contact_name in contacts:
self.add_link('Contact', contact_name)

View file

@ -202,6 +202,8 @@ class TestCommunication(unittest.TestCase):
self.assertIn(("Note", note.name), doc_links)
def create_email_account():
frappe.delete_doc_if_exists("Email Account", "_Test Comm Account 1")
frappe.flags.mute_emails = False
frappe.flags.sent_mail = None

View file

@ -84,7 +84,7 @@ class ScheduledJobType(Document):
def log_status(self, status):
# log file
frappe.logger(__name__).info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site))
frappe.logger("scheduler").info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site))
self.update_scheduler_log(status)
def update_scheduler_log(self, status):

View file

@ -46,6 +46,9 @@ class CustomField(Document):
if not self.fieldname:
frappe.throw(_("Fieldname not set for Custom Field"))
if self.fieldname in fieldnames:
frappe.throw(_("A field with the name '{}' already exists in doctype {}.").format(self.fieldname, self.dt))
if self.get('translatable', 0) and not supports_translation(self.fieldtype):
self.translatable = 0

View file

@ -251,6 +251,7 @@ frappe.ui.form.on('Dashboard Chart', {
render_filters_table: function(frm) {
frm.set_df_property("filters_section", "hidden", 0);
let is_document_type = frm.doc.chart_type!== 'Report' && frm.doc.chart_type!=='Custom';
let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default;
let wrapper = $(frm.get_field('filters_json').wrapper).empty();
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
@ -268,6 +269,18 @@ frappe.ui.form.on('Dashboard Chart', {
let filters = JSON.parse(frm.doc.filters_json || '[]');
var filters_set = false;
// Set dynamic filters for reports
if (frm.doc.chart_type == 'Report') {
let set_filters = false;
frm.chart_filters.forEach(f => {
if (is_dynamic_filter(f)) {
filters[f.fieldname] = f.default;
set_filters = true;
}
});
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
}
let fields;
if (is_document_type) {
fields = [
@ -292,6 +305,7 @@ frappe.ui.form.on('Dashboard Chart', {
}
} else if (frm.chart_filters.length) {
fields = frm.chart_filters.filter(f => f.fieldname);
fields.map( f => {
if (filters[f.fieldname]) {
let condition = '=';
@ -318,7 +332,7 @@ frappe.ui.form.on('Dashboard Chart', {
let dialog = new frappe.ui.Dialog({
title: __('Set Filters'),
fields: fields,
fields: fields.filter(f => !is_dynamic_filter(f)),
primary_action: function() {
let values = this.get_values();
if (values) {
@ -351,10 +365,15 @@ frappe.ui.form.on('Dashboard Chart', {
}
dialog.show();
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
frappe.query_reports[frm.doc.report_name].onload
&& frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
if (frm.doc.chart_type == 'Report') {
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
frappe.query_reports[frm.doc.report_name]
&& frappe.query_reports[frm.doc.report_name].onload
&& frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
}
dialog.set_values(filters);
});
},

View file

@ -20,6 +20,17 @@ class DeskPage(Document):
if frappe.conf.developer_mode and self.is_standard:
export_to_files(record_list=[['Desk Page', self.name]], record_module=self.module)
@staticmethod
def get_module_page_map():
filters = {
'extends_another_page': 0,
'for_user': '',
}
pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1)
return { page[1]: page[0] for page in pages }
def disable_saving_as_standard():
return frappe.flags.in_install or \
frappe.flags.in_patch or \

View file

@ -3,10 +3,43 @@
frappe.ui.form.on('Notification Log', {
refresh: function(frm) {
let dt = frm.doc.document_type;
let dn = frm.doc.document_name;
frm.fields_dict.document_name.$input_wrapper
.find('.control-value')
.wrapInner(`<a href='#Form/${dt}/${dn}'></a>`);
if (frm.doc.attached_file) {
frm.trigger('set_attachment');
} else {
frm.get_field('attachment_link').$wrapper.empty();
}
},
open_reference_document: function(frm) {
const dt = frm.doc.document_type;
const dn = frm.doc.document_name;
frappe.set_route('Form', dt, dn);
},
set_attachment: function(frm) {
const attachment = JSON.parse(frm.doc.attached_file);
const $wrapper = frm.get_field('attachment_link').$wrapper;
$wrapper.html(`
<div class="attached-file text-medium">
<div class="ellipsis">
<i class="fa fa-paperclip"></i>
<a class="attached-file-link">${attachment.name}.pdf</a>
</div>
</div>
`);
$wrapper.find(".attached-file-link").click(() => {
const w = window.open(
frappe.urllib.get_full_url(`/api/method/frappe.utils.print_format.download_pdf?
doctype=${encodeURIComponent(attachment.doctype)}
&name=${encodeURIComponent(attachment.name)}
&format=${encodeURIComponent(attachment.print_format)}
&lang=${encodeURIComponent(attachment.lang)}`)
);
if (!w) {
frappe.msgprint(__("Please enable pop-ups"));
}
});
}
});

View file

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2019-08-26 13:37:34.165254",
"doctype": "DocType",
"editable_grid": 1,
@ -8,10 +9,12 @@
"for_user",
"type",
"email_content",
"column_break_4",
"document_type",
"read",
"document_name",
"attached_file",
"attachment_link",
"open_reference_document",
"from_user"
],
"fields": [
@ -20,57 +23,65 @@
"fieldtype": "Text",
"in_list_view": 1,
"label": "Subject",
"read_only": 1
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "for_user",
"fieldtype": "Link",
"hidden": 1,
"label": "For User",
"options": "User",
"read_only": 1
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "type",
"fieldtype": "Select",
"hidden": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Type",
"options": "Mention\nEnergy Point\nAssignment\nShare",
"read_only": 1,
"search_index": 1
"options": "Mention\nEnergy Point\nAssignment\nShare\nAlert",
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "email_content",
"fieldtype": "Text",
"label": "Email Content",
"read_only": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
"fieldtype": "Text Editor",
"label": "Message",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "document_type",
"fieldtype": "Link",
"hidden": 1,
"label": "Document Type",
"options": "DocType",
"read_only": 1,
"search_index": 1
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "document_name",
"fieldtype": "Data",
"label": "Document Name",
"read_only": 1,
"search_index": 1
"hidden": 1,
"label": "Document Link",
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "from_user",
"fieldtype": "Link",
"hidden": 1,
"label": "From User",
"options": "User",
"read_only": 1,
"search_index": 1
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
@ -78,26 +89,51 @@
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Read"
"label": "Read",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "open_reference_document",
"fieldtype": "Button",
"label": "Open Reference Document",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "attached_file",
"fieldtype": "Code",
"hidden": 1,
"label": "Attached File",
"options": "JSON",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "attachment_link",
"fieldtype": "HTML",
"label": "Attachment Link",
"show_days": 1,
"show_seconds": 1
}
],
"hide_toolbar": 1,
"in_create": 1,
"modified": "2019-11-12 15:22:35.283678",
"links": [],
"modified": "2020-05-31 22:31:12.886950",
"modified_by": "umair@erpnext.com",
"module": "Desk",
"name": "Notification Log",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1,
"write": 1
"share": 1
}
],
"sort_field": "modified",

View file

@ -48,6 +48,7 @@ def enqueue_create_notification(users, doc):
if isinstance(users, frappe.string_types):
users = [user.strip() for user in users.split(',') if user.strip()]
users = list(set(users))
frappe.enqueue(
'frappe.desk.doctype.notification_log.notification_log.make_notification_logs',
@ -58,6 +59,7 @@ def enqueue_create_notification(users, doc):
def make_notification_logs(doc, users):
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
for user in users:
if frappe.db.exists('User', user):
if is_notifications_enabled(user):
@ -68,7 +70,7 @@ def make_notification_logs(doc, users):
_doc.update(doc)
_doc.for_user = user
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point':
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert':
_doc.insert(ignore_permissions=True)
def send_notification_email(doc):

View file

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "Prompt",
"creation": "2019-09-11 22:15:44.851526",
"doctype": "DocType",
@ -21,52 +22,68 @@
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
"label": "Enabled",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "subscribed_documents",
"fieldtype": "Table MultiSelect",
"label": "Subscribed Documents",
"options": "Notification Subscribed Document"
"options": "Notification Subscribed Document",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Section Break",
"label": "Email Settings"
"label": "Email Settings",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"fieldname": "enable_email_notifications",
"fieldtype": "Check",
"label": "Enable Email Notifications"
"label": "Enable Email Notifications",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"depends_on": "enable_email_notifications",
"fieldname": "enable_email_mention",
"fieldtype": "Check",
"label": "Mentions"
"label": "Mentions",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"depends_on": "enable_email_notifications",
"fieldname": "enable_email_assignment",
"fieldtype": "Check",
"label": "Assignments"
"label": "Assignments",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"depends_on": "enable_email_notifications",
"fieldname": "enable_email_energy_point",
"fieldtype": "Check",
"label": "Energy Points"
"label": "Energy Points",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"depends_on": "enable_email_notifications",
"fieldname": "enable_email_share",
"fieldtype": "Check",
"label": "Document Share"
"label": "Document Share",
"show_days": 1,
"show_seconds": 1
},
{
"default": "__user",
@ -75,18 +92,23 @@
"hidden": 1,
"label": "User",
"options": "User",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"label": "Seen"
"label": "Seen",
"show_days": 1,
"show_seconds": 1
}
],
"in_create": 1,
"modified": "2019-11-19 12:57:59.356786",
"links": [],
"modified": "2020-05-31 22:16:40.798019",
"modified_by": "Administrator",
"module": "Desk",
"name": "Notification Settings",

View file

@ -28,6 +28,9 @@ def is_email_notifications_enabled_for_type(user, notification_type):
if not is_email_notifications_enabled(user):
return False
if notification_type == 'Alert':
return False
fieldname = 'enable_email_' + frappe.scrub(notification_type)
enabled = frappe.db.get_value('Notification Settings', user, fieldname)
if enabled is None:

View file

@ -110,7 +110,11 @@ class UserProfile {
render_line_chart() {
this.line_chart_filters = [['Energy Point Log', 'user', '=', this.user_id, false]];
this.line_chart_filters = [
['Energy Point Log', 'user', '=', this.user_id, false],
['Energy Point Log', 'type', '!=', 'Review', false]
];
this.line_chart_config = {
timespan: 'Last Month',
time_interval: 'Daily',
@ -186,7 +190,10 @@ class UserProfile {
options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'],
action: (selected_item) => {
if (selected_item === 'All') {
if (this.line_chart_filters.length > 1) this.line_chart_filters.pop();
this.line_chart_filters = [
['Energy Point Log', 'user', '=', this.user_id, false],
['Energy Point Log', 'type', '!=', 'Review', false]
];
} else {
this.line_chart_filters[1] = ['Energy Point Log', 'type', '=', selected_item, false];
}

View file

@ -29,6 +29,7 @@
"default_incoming",
"email_sync_option",
"initial_sync_count",
"create_contact",
"section_break_12",
"enable_automatic_linking",
"section_break_13",
@ -114,9 +115,9 @@
"depends_on": "eval:!doc.service",
"fieldname": "domain",
"fieldtype": "Link",
"label": "Domain",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Domain",
"options": "Email Domain"
},
{
@ -408,11 +409,17 @@
"fieldname": "use_ssl_for_outgoing",
"fieldtype": "Check",
"label": "Use SSL for Outgoing"
},
{
"default": "1",
"fieldname": "create_contact",
"fieldtype": "Check",
"label": "Create Contacts from Incoming Emails"
}
],
"icon": "fa fa-inbox",
"links": [],
"modified": "2020-04-06 19:20:50.491146",
"modified": "2020-05-11 15:18:43.931499",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",
@ -427,11 +434,11 @@
"write": 1
},
{
"read": 1,
"role": "Inbox User"
"read": 1,
"role": "Inbox User"
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View file

@ -80,7 +80,6 @@ frappe.ui.form.on("Notification", {
});
},
refresh: function(frm) {
frm.toggle_reqd("recipients", frm.doc.channel=="Email");
frappe.notification.setup_fieldname_select(frm);
frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
frm.trigger('event');

View file

@ -1,4 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2014-07-11 17:18:09.923399",
@ -22,6 +23,7 @@
"days_in_advance",
"value_changed",
"sender",
"send_system_notification",
"sender_email",
"section_break_9",
"condition",
@ -46,32 +48,43 @@
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
"label": "Enabled",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"default": "Email",
"depends_on": "eval: !doc.disable_channel",
"fieldname": "channel",
"fieldtype": "Select",
"label": "Channel",
"options": "Email\nSlack",
"options": "Email\nSlack\nSystem Notification",
"reqd": 1,
"set_only_once": 1
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.channel=='Slack'",
"fieldname": "slack_webhook_url",
"fieldtype": "Link",
"label": "Slack Channel",
"options": "Slack Webhook URL"
"mandatory_depends_on": "eval:doc.channel=='Slack'",
"options": "Slack Webhook URL",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "filters",
"fieldtype": "Section Break",
"label": "Filters"
"label": "Filters",
"show_days": 1,
"show_seconds": 1
},
{
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>{{ doc.name }} Delivered</code></pre></div>",
@ -80,7 +93,9 @@
"ignore_xss_filter": 1,
"in_list_view": 1,
"label": "Subject",
"reqd": 1
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "document_type",
@ -90,13 +105,17 @@
"label": "Document Type",
"options": "DocType",
"reqd": 1,
"search_index": 1
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "is_standard",
"fieldtype": "Check",
"label": "Is Standard"
"label": "Is Standard",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "is_standard",
@ -104,11 +123,15 @@
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Module",
"options": "Module Def"
"options": "Module Def",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "col_break_1",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "event",
@ -117,21 +140,27 @@
"label": "Send Alert On",
"options": "\nNew\nSave\nSubmit\nCancel\nDays After\nDays Before\nValue Change\nMethod\nCustom",
"reqd": 1,
"search_index": 1
"search_index": 1,
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.event=='Method'",
"description": "Trigger on valid methods like \"before_insert\", \"after_update\", etc (will depend on the DocType selected)",
"fieldname": "method",
"fieldtype": "Data",
"label": "Trigger Method"
"label": "Trigger Method",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.event==\"Days After\" || doc.event==\"Days Before\"",
"description": "Send alert if date matches this field's value",
"fieldname": "date_changed",
"fieldtype": "Select",
"label": "Reference Date"
"label": "Reference Date",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
@ -139,31 +168,41 @@
"description": "Send days before or after the reference date",
"fieldname": "days_in_advance",
"fieldtype": "Int",
"label": "Days Before or After"
"label": "Days Before or After",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.event==\"Value Change\"",
"description": "Send alert if this field's value changes",
"fieldname": "value_changed",
"fieldtype": "Select",
"label": "Value Changed"
"label": "Value Changed",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "sender",
"fieldtype": "Link",
"label": "Sender",
"options": "Email Account"
"options": "Email Account",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "sender_email",
"fieldtype": "Data",
"label": "Sender Email",
"options": "Email",
"read_only": 1
"read_only": 1,
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
},
{
"description": "Optional: The alert will be sent if this expression is true",
@ -171,99 +210,143 @@
"fieldtype": "Code",
"ignore_xss_filter": 1,
"in_list_view": 1,
"label": "Condition"
"label": "Condition",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "html_7",
"fieldtype": "HTML",
"options": "<p><strong>Condition Examples:</strong></p>\n<pre>doc.status==\"Open\"<br>doc.due_date==nowdate()<br>doc.total &gt; 40000\n</pre>\n"
"options": "<p><strong>Condition Examples:</strong></p>\n<pre>doc.status==\"Open\"<br>doc.due_date==nowdate()<br>doc.total &gt; 40000\n</pre>\n",
"show_days": 1,
"show_seconds": 1
},
{
"collapsible": 1,
"fieldname": "property_section",
"fieldtype": "Section Break",
"label": "Set Property After Alert"
"label": "Set Property After Alert",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "set_property_after_alert",
"fieldtype": "Select",
"label": "Set Property After Alert"
"label": "Set Property After Alert",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "property_value",
"fieldtype": "Data",
"label": "Value To Be Set"
"label": "Value To Be Set",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.channel=='Email'",
"depends_on": "eval:doc.channel!=='Slack'",
"fieldname": "column_break_5",
"fieldtype": "Section Break",
"label": "Recipients"
"label": "Recipients",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "recipients",
"fieldtype": "Table",
"label": "Recipients",
"options": "Notification Recipient"
"mandatory_depends_on": "eval:doc.channel!=='Slack'",
"options": "Notification Recipient",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "message_sb",
"fieldtype": "Section Break",
"label": "Message"
"label": "Message",
"show_days": 1,
"show_seconds": 1
},
{
"default": "Add your message here",
"fieldname": "message",
"fieldtype": "Code",
"ignore_xss_filter": 1,
"label": "Message"
"label": "Message",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.channel=='Email'",
"fieldname": "message_examples",
"fieldtype": "HTML",
"label": "Message Examples",
"options": "<h5>Message Example</h5>\n\n<pre>&lt;h3&gt;Order Overdue&lt;/h3&gt;\n\n&lt;p&gt;Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.&lt;/p&gt;\n\n&lt;!-- show last comment --&gt;\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n&lt;h4&gt;Details&lt;/h4&gt;\n\n&lt;ul&gt;\n&lt;li&gt;Customer: {{ doc.customer }}\n&lt;li&gt;Amount: {{ doc.grand_total }}\n&lt;/ul&gt;\n</pre>"
"options": "<h5>Message Example</h5>\n\n<pre>&lt;h3&gt;Order Overdue&lt;/h3&gt;\n\n&lt;p&gt;Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.&lt;/p&gt;\n\n&lt;!-- show last comment --&gt;\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n&lt;h4&gt;Details&lt;/h4&gt;\n\n&lt;ul&gt;\n&lt;li&gt;Customer: {{ doc.customer }}\n&lt;li&gt;Amount: {{ doc.grand_total }}\n&lt;/ul&gt;\n</pre>",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "eval:doc.channel=='Slack'",
"fieldname": "slack_message_examples",
"fieldtype": "HTML",
"label": "Message Examples",
"options": "<h5>Message Example</h5>\n\n<pre>*Order Overdue*\n\nTransaction {{ doc.name }} has exceeded Due Date. Please take necessary action.\n\n<!-- show last comment -->\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n*Details*\n\n\u2022 Customer: {{ doc.customer }}\n\u2022 Amount: {{ doc.grand_total }}\n</pre>"
"options": "<h5>Message Example</h5>\n\n<pre>*Order Overdue*\n\nTransaction {{ doc.name }} has exceeded Due Date. Please take necessary action.\n\n<!-- show last comment -->\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n*Details*\n\n\u2022 Customer: {{ doc.customer }}\n\u2022 Amount: {{ doc.grand_total }}\n</pre>",
"show_days": 1,
"show_seconds": 1
},
{
"fieldname": "view_properties",
"fieldtype": "Button",
"label": "View Properties (via Customize Form)"
"label": "View Properties (via Customize Form)",
"show_days": 1,
"show_seconds": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "attach_print",
"fieldname": "column_break_25",
"fieldtype": "Section Break",
"label": "Print Settings"
"label": "Print Settings",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "attach_print",
"fieldtype": "Check",
"label": "Attach Print"
"label": "Attach Print",
"show_days": 1,
"show_seconds": 1
},
{
"depends_on": "attach_print",
"fieldname": "print_format",
"fieldtype": "Link",
"label": "Print Format",
"options": "Print Format"
"options": "Print Format",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"depends_on": "eval: doc.channel !== 'System Notification'",
"description": "If enabled, the notification will show up in the notifications dropdown on the top right corner of the navigation bar.",
"fieldname": "send_system_notification",
"fieldtype": "Check",
"label": "Send System Notification",
"show_days": 1,
"show_seconds": 1
}
],
"icon": "fa fa-envelope",
"modified": "2019-07-15 13:17:02.585013",
"links": [],
"modified": "2020-05-29 16:03:10.914526",
"modified_by": "Administrator",
"module": "Email",
"name": "Notification",

View file

@ -13,6 +13,7 @@ from frappe.utils.jinja import validate_template
from frappe.modules.utils import export_module_json, get_doc_module
from six import string_types
from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message
from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification
class Notification(Document):
def onload(self):
@ -125,6 +126,9 @@ def get_context(context):
if self.channel == 'Slack':
self.send_a_slack_msg(doc, context)
if self.channel == 'System Notification' or self.send_system_notification:
self.create_system_notification(doc, context)
if self.set_property_after_alert:
allow_update = True
if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit:
@ -143,6 +147,25 @@ def get_context(context):
except Exception:
frappe.log_error(title='Document update failed', message=frappe.get_traceback())
def create_system_notification(self, doc, context):
subject = self.subject
if "{" in subject:
subject = frappe.render_template(self.subject, context)
attachments = self.get_attachment(doc)
recipients, cc, bcc = self.get_list_of_recipients(doc, context)
users = recipients + cc + bcc
notification_doc = {
'type': 'Alert',
'document_type': doc.doctype,
'document_name': doc.name,
'subject': subject,
'email_content': frappe.render_template(self.message, context),
'attached_file': attachments and json.dumps(attachments[0])
}
enqueue_create_notification(users, notification_doc)
def send_an_email(self, doc, context):
from email.utils import formataddr
subject = self.subject
@ -228,8 +251,7 @@ def get_context(context):
# ignoring attachment as draft and cancelled documents are not allowed to print
status = "Draft" if doc.docstatus == 0 else "Cancelled"
frappe.throw(_("""Not allowed to attach {0} document,
please enable Allow Print For {0} in Print Settings""".format(status)),
frappe.throw(_("""Not allowed to attach {0} document, please enable Allow Print For {0} in Print Settings""").format(status),
title=_("Error in Notification"))
else:
return [{

View file

@ -269,6 +269,7 @@ def make_site_dirs():
os.path.join(site_private_path, 'backups'),
os.path.join(site_public_path, 'files'),
os.path.join(site_private_path, 'files'),
os.path.join(frappe.local.site_path, 'logs'),
os.path.join(frappe.local.site_path, 'task-logs')):
if not os.path.exists(dir_path):
os.makedirs(dir_path)

View file

@ -90,7 +90,7 @@ def backup_to_dropbox(upload_db_backup=True):
dropbox_settings['access_token'] = access_token['oauth2_token']
set_dropbox_access_token(access_token['oauth2_token'])
dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'])
dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'], timeout=None)
if upload_db_backup:
if frappe.flags.create_new_backup:

View file

@ -147,11 +147,14 @@ def sync_contacts_from_google_contacts(g_contact):
results = []
contacts_updated = 0
sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None
contacts = frappe._dict()
while True:
try:
sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None
contacts = google_contacts.people().connections().list(resourceName='people/me',syncToken=sync_token,
personFields="names,emailAddresses,organizations,phoneNumbers").execute()
contacts = google_contacts.people().connections().list(resourceName='people/me', pageToken=contacts.get("nextPageToken"),
syncToken=sync_token, pageSize=2000, requestSyncToken=True, personFields="names,emailAddresses,organizations,phoneNumbers").execute()
except HttpError as err:
frappe.throw(_("Google Contacts - Could not sync contacts from Google Contacts {0}, error code {1}.").format(account.name, err.resp.status))

View file

@ -64,6 +64,8 @@ from __future__ import unicode_literals
import frappe
from frappe import _
import json
import hmac
import hashlib
from six.moves.urllib.parse import urlencode
from frappe.model.document import Document
from frappe.utils import get_url, call_hook_method, cint, get_timestamp
@ -317,6 +319,20 @@ class RazorpaySettings(Document):
except Exception:
frappe.log_error(frappe.get_traceback())
def verify_signature(self, body, signature, key):
key = bytes(key, 'utf-8')
body = bytes(body, 'utf-8')
dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256)
generated_signature = dig.hexdigest()
result = hmac.compare_digest(generated_signature, signature)
if not result:
frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError)
return result
def capture_payment(is_sandbox=False, sanbox_response=None):
"""
Verifies the purchase as complete by the merchant.

View file

@ -693,7 +693,7 @@ class BaseDocument(object):
df = self.meta.get_field(fieldname)
sanitized_value = value
if df and df.get("fieldtype") in ("Data", "Code", "Small Text") and df.get("options")=="Email":
if df and df.get("fieldtype") in ("Data", "Code", "Small Text", "Text") and df.get("options")=="Email":
sanitized_value = sanitize_email(value)
elif df and (df.get("ignore_xss_filter")

View file

@ -299,6 +299,7 @@ def set_workflow_state_on_action(doc, workflow_name, action):
return
action_map = {
'update_after_submit': '1',
'submit': '1',
'cancel': '2'
}

View file

@ -278,6 +278,7 @@ frappe.patches.v13_0.set_path_for_homepage_in_web_page_view
frappe.patches.v13_0.migrate_translation_column_data
frappe.patches.v13_0.set_read_times
frappe.patches.v13_0.remove_web_view
frappe.patches.v13_0.site_wise_logging
frappe.patches.v13_0.set_unique_for_page_view
frappe.patches.v13_0.remove_tailwind_from_page_builder
frappe.patches.v13_0.rename_onboarding
@ -285,4 +286,4 @@ frappe.patches.v13_0.email_unsubscribe
execute:frappe.delete_doc("Web Template", "Section with Left Image", force=1)
execute:frappe.delete_doc("DocType", "Onboarding Slide")
execute:frappe.delete_doc("DocType", "Onboarding Slide Field")
execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link")
execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link")

View file

@ -0,0 +1,10 @@
import os
import frappe
def execute():
site = frappe.local.site
log_folder = os.path.join(site, 'logs')
if not os.path.exists(log_folder):
os.mkdir(log_folder)

View file

@ -650,13 +650,14 @@ frappe.ui.form.Form = class FrappeForm {
frappe.utils.play_sound("submit");
callback && callback();
me.script_manager.trigger("on_submit")
.then(() => resolve(me));
if (frappe.route_hooks.after_submit) {
let route_callback = frappe.route_hooks.after_submit;
delete frappe.route_hooks.after_submit;
route_callback(me);
}
.then(() => resolve(me))
.then(() => {
if (frappe.route_hooks.after_submit) {
let route_callback = frappe.route_hooks.after_submit;
delete frappe.route_hooks.after_submit;
route_callback(me);
}
});
}
}, btn, () => me.handle_save_fail(btn, on_error), resolve);
});
@ -1586,7 +1587,7 @@ frappe.ui.form.Form = class FrappeForm {
let steps = frappe.tour[this.doctype].map(step => {
let field = this.get_docfield(step.fieldname);
return {
element: `.frappe-control[title='${step.fieldname}']`,
element: `.frappe-control[data-fieldname='${step.fieldname}']`,
popover: {
title: step.title || field.label,
description: step.description

View file

@ -16,12 +16,22 @@ frappe.ui.form.get_event_handler_list = function(doctype, fieldname) {
frappe.ui.form.on = frappe.ui.form.on_change = function(doctype, fieldname, handler) {
var add_handler = function(fieldname, handler) {
var handler_list = frappe.ui.form.get_event_handler_list(doctype, fieldname);
handler_list.push(handler);
let _handler = (...args) => {
try {
handler(...args);
} catch (error) {
console.error(handler);
throw error;
}
}
handler_list.push(_handler);
// add last handler to events so it can be called as
// frm.events.handler(frm)
if(cur_frm && cur_frm.doctype===doctype) {
cur_frm.events[fieldname] = handler;
cur_frm.events[fieldname] = _handler;
}
}

View file

@ -287,7 +287,8 @@ frappe.socketio.SocketIOUploader = class SocketIOUploader {
}
function fallback_required() {
return !frappe.boot.sysdefaults.use_socketio_to_upload_file || !frappe.socketio.socket.connected;
return !frappe.socketio.socket.connected
|| !( !frappe.boot.sysdefaults || frappe.boot.sysdefaults.use_socketio_to_upload_file );
}
if (fallback_required()) {

View file

@ -304,10 +304,7 @@ frappe.ui.Notifications = class Notifications {
}
get_dropdown_item_html(field) {
let doc_link = frappe.utils.get_form_link(
field.document_type,
field.document_name
);
let doc_link = this.get_item_link(field);
let read_class = field.read ? '' : 'unread';
let mark_read_action = field.read ? '': 'data-action="mark_as_read"';
let message = field.subject;
@ -336,6 +333,17 @@ frappe.ui.Notifications = class Notifications {
return item_html;
}
get_item_link(notification_doc) {
const link_doctype =
notification_doc.type == 'Alert' ? 'Notification Log': notification_doc.document_type;
const link_docname =
notification_doc.type == 'Alert' ? notification_doc.name: notification_doc.document_name;
return frappe.utils.get_form_link(
link_doctype,
link_docname
);
}
render_dropdown_headers() {
this.categories = [
{

View file

@ -811,19 +811,19 @@ Object.assign(frappe.utils, {
let total_duration = frappe.utils.seconds_to_duration(value, duration_options);
if (total_duration.days) {
duration += total_duration.days + 'd';
duration += total_duration.days + __('d', null, 'Days (Field: Duration)');
}
if (total_duration.hours) {
duration += (duration.length ? " " : "");
duration += total_duration.hours + 'h';
duration += total_duration.hours + __('h', null, 'Hours (Field: Duration)');
}
if (total_duration.minutes) {
duration += (duration.length ? " " : "");
duration += total_duration.minutes + 'm';
duration += total_duration.minutes + __('m', null, 'Minutes (Field: Duration)');
}
if (total_duration.seconds) {
duration += (duration.length ? " " : "");
duration += total_duration.seconds + 's';
duration += total_duration.seconds + __('s', null, 'Seconds (Field: Duration)');
}
}
return duration;
@ -998,4 +998,4 @@ String.prototype.plural = function(revert) {
}
return this;
};
};

View file

@ -1,38 +0,0 @@
frappe.ui.form.on("Web Page Block", {
edit_values(frm, cdt, cdn) {
let row = frm.selected_doc;
frappe.model.with_doc("Web Template", row.web_template).then((doc) => {
let d = new frappe.ui.Dialog({
title: __("Edit Values"),
fields: doc.fields.map((df) => {
if (df.fieldtype == "Section Break") {
df.collapsible = 1;
}
return df;
}),
primary_action(values) {
frappe.model.set_value(
cdt,
cdn,
"web_template_values",
JSON.stringify(values)
);
d.hide();
},
});
let values = JSON.parse(row.web_template_values || "{}");
d.set_values(values);
d.show();
d.sections.forEach((sect) => {
let fields_with_value = sect.fields_list.filter(
(field) => values[field.df.fieldname]
);
if (fields_with_value.length) {
sect.collapse(false);
}
});
});
},
});

View file

@ -89,16 +89,21 @@ frappe.breadcrumbs = {
breadcrumbs.module = frappe.breadcrumbs.module_map[breadcrumbs.module];
}
if(frappe.get_module(breadcrumbs.module)) {
let current_module = breadcrumbs.module
// Check if a desk page exists
if (frappe.boot.module_page_map[breadcrumbs.module]) {
breadcrumbs.module = frappe.boot.module_page_map[breadcrumbs.module];
}
if(frappe.get_module(current_module)) {
// if module access exists
var module_info = frappe.get_module(breadcrumbs.module),
var module_info = frappe.get_module(current_module),
icon = module_info && module_info.icon,
label = module_info ? module_info.label : breadcrumbs.module;
if(module_info && !module_info.blocked && frappe.visible_modules.includes(module_info.module_name)) {
$(repl('<li><a href="#workspace/%(module)s">%(label)s</a></li>',
{ module: breadcrumbs.module, label: __(label) }))
{ module: breadcrumbs.module, label: __(breadcrumbs.module) }))
.appendTo($breadcrumbs);
}
}

View file

@ -97,7 +97,6 @@ export default class ChartWidget extends Widget {
this.chart_settings = {};
}
this.setup_container();
this.prepare_chart_object();
if (!this.in_customize_mode) {
this.action_area.empty();
this.prepare_chart_actions();
@ -110,7 +109,10 @@ export default class ChartWidget extends Widget {
this.render_time_series_filters();
}
}
this.fetch_and_update_chart();
frappe.run_serially([
() => this.prepare_chart_object(),
() => this.fetch_and_update_chart(),
]);
});
}
@ -625,13 +627,41 @@ export default class ChartWidget extends Widget {
}
prepare_chart_object() {
let saved_filters = this.chart_settings.filters || null;
this.filters =
saved_filters || this.filters || JSON.parse(this.chart_doc.filters_json || "[]");
if (this.chart_doc.type == 'Heatmap' && !this.chart_doc.heatmap_year) {
this.chart_doc.heatmap_year = frappe.dashboard_utils.get_year(frappe.datetime.now_date());
}
return this.set_chart_filters();
}
set_chart_filters() {
let user_saved_filters = this.chart_settings.filters || null;
let chart_saved_filters = JSON.parse(this.chart_doc.filters_json || "null");
if (this.chart_doc.chart_type == 'Report') {
return frappe.dashboard_utils
.get_filters_for_chart_type(this.chart_doc).then(filters => {
chart_saved_filters = this.update_default_date_filters(filters, chart_saved_filters);
this.filters =
user_saved_filters || this.filters || chart_saved_filters;
});
} else {
this.filters =
user_saved_filters || this.filters || chart_saved_filters;
return Promise.resolve();
}
}
update_default_date_filters(report_filters, chart_filters) {
report_filters.map(f => {
if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) {
if (f.reqd || chart_filters[f.fieldname]) {
chart_filters[f.fieldname] = f.default;
}
}
});
return chart_filters;
}
get_settings() {

View file

@ -29,9 +29,13 @@ export default class ShortcutWidget extends Widget {
name: this.link_to,
type: this.type,
is_query_report: this.is_query_report,
doctype: this.ref_doctype
doctype: this.ref_doctype,
});
let filters = this.get_doctype_filter();
if (this.type == "DocType" && filters) {
frappe.route_options = filters;
}
frappe.set_route(route);
});
}
@ -40,16 +44,26 @@ export default class ShortcutWidget extends Widget {
if (this.in_customize_mode) return;
this.widget.addClass("shortcut-widget-box");
const get_filter = new Function(`return ${this.stats_filter}`);
if (this.type == "DocType" && this.stats_filter) {
let filters = this.get_doctype_filter();
if (this.type == "DocType" && filters) {
frappe.db
.count(this.link_to, {
filters: get_filter(),
filters: filters,
})
.then((count) => this.set_count(count));
}
}
get_doctype_filter() {
let count_filter = new Function(`return ${this.stats_filter}`)();
if (count_filter) {
return count_filter;
}
return null;
}
set_title() {
if (this.icon) {
this.title_field[0].innerHTML = `<div>
@ -82,4 +96,4 @@ export default class ShortcutWidget extends Widget {
buttons.appendTo(this.action_area);
}
}
}

View file

@ -241,11 +241,14 @@ class ShortcutDialog extends WidgetDialog {
if (this.dialog.get_value("type") == "DocType" && this.filter_group) {
let filters = this.filter_group.get_filters();
filters.forEach((arr) => {
stats_filter[arr[1]] = [arr[2], arr[3]];
});
data.stats_filter = JSON.stringify(stats_filter);
if (filters.length) {
filters.forEach((arr) => {
stats_filter[arr[1]] = [arr[2], arr[3]];
});
data.stats_filter = JSON.stringify(stats_filter);
}
}
data.label = data.label

View file

@ -6,6 +6,7 @@
from __future__ import unicode_literals, print_function
from werkzeug.test import Client
import os, re, sys, json, hashlib, requests, traceback
import functools
from .html_utils import sanitize_html
import frappe
from frappe.utils.identicon import Identicon
@ -360,6 +361,7 @@ def decode_dict(d, encoding="utf-8"):
return d
@functools.lru_cache()
def get_site_name(hostname):
return hostname.split(':')[0]

View file

@ -89,10 +89,14 @@ def sync_dashboards(app=None):
config = get_config(app_name, module_name)
if config:
frappe.flags.in_import = True
make_records(config.charts, "Dashboard Chart")
make_records(config.number_cards, "Number Card")
make_records(config.dashboards, "Dashboard")
frappe.flags.in_import = False
try:
make_records(config.charts, "Dashboard Chart")
make_records(config.number_cards, "Number Card")
make_records(config.dashboards, "Dashboard")
except Exception as e:
frappe.log_error(e, _("Dashboard Import Error"))
finally:
frappe.flags.in_import = False
def make_records(config, doctype):
if not config:

View file

@ -21,7 +21,7 @@ def make_error_snapshot(exception):
if frappe.conf.disable_error_snapshot:
return
logger = frappe.logger(__name__, with_more_info=False)
logger = frappe.logger(with_more_info=True)
try:
error_id = '{timestamp:s}-{ip:s}-{hash:s}'.format(

View file

@ -1,30 +1,53 @@
# imports - compatibility imports
from __future__ import unicode_literals
import frappe
# imports - standard imports
import logging
import os
from logging.handlers import RotatingFileHandler
# imports - third party imports
from six import text_type
default_log_level = logging.DEBUG
LOG_FILENAME = '../logs/{}-frappe.log'.format(frappe.local.site)
# imports - module imports
import frappe
def get_logger(module, with_more_info=True):
default_log_level = logging.DEBUG
site = getattr(frappe.local, 'site', None)
def get_logger(module, with_more_info=False):
global site
if module in frappe.loggers:
return frappe.loggers[module]
formatter = logging.Formatter('[%(levelname)s] %(asctime)s | %(pathname)s:\n%(message)s')
# handler = logging.StreamHandler()
if not module:
module = "frappe"
with_more_info = True
handler = RotatingFileHandler(
LOG_FILENAME, maxBytes=100000, backupCount=20)
handler.setFormatter(formatter)
logfile = module + '.log'
site = getattr(frappe.local, 'site', None)
LOG_FILENAME = os.path.join('..', 'logs', logfile)
logger = logging.getLogger(module)
logger.setLevel(frappe.log_level or default_log_level)
logger.propagate = False
formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s')
handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20)
logger.addHandler(handler)
#
if site:
SITELOG_FILENAME = os.path.join(site, 'logs', logfile)
site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20)
site_handler.setFormatter(formatter)
logger.addHandler(site_handler)
if with_more_info:
handler.addFilter(SiteContextFilter())
logger = logging.getLogger(module)
logger.setLevel(frappe.log_level or default_log_level)
logger.addHandler(handler)
logger.propagate = False
handler.setFormatter(formatter)
frappe.loggers[module] = logger
@ -33,25 +56,9 @@ def get_logger(module, with_more_info=True):
class SiteContextFilter(logging.Filter):
"""This is a filter which injects request information (if available) into the log."""
def filter(self, record):
record.msg = get_more_info_for_log() + text_type(record.msg)
return True
def get_more_info_for_log():
'''Adds Site, Form Dict into log entry'''
more_info = []
site = getattr(frappe.local, 'site', None)
if site:
more_info.append('Site: {0}'.format(site))
form_dict = getattr(frappe.local, 'form_dict', None)
if form_dict:
more_info.append('Form Dict: {0}'.format(frappe.as_json(form_dict)))
if more_info:
# to append a \n
more_info = more_info + ['']
return '\n'.join(more_info)
if "Form Dict" not in text_type(record.msg):
record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, getattr(frappe.local, 'form_dict', None))
return True
def set_log_level(level):
'''Use this method to set log level to something other than the default DEBUG'''

View file

@ -7,17 +7,24 @@ Events:
monthly
weekly
"""
# imports - compatibility imports
from __future__ import print_function, unicode_literals
from __future__ import unicode_literals, print_function
# imports - standard imports
import os
import time
import frappe, os, time
# imports - third party imports
import schedule
from frappe.utils import now_datetime, get_datetime
from frappe.utils import get_sites
from frappe.installer import update_site_config
# imports - module imports
import frappe
from frappe.core.doctype.user.user import STANDARD_USERS
from frappe.installer import update_site_config
from frappe.utils import get_sites, now_datetime
from frappe.utils.background_jobs import get_jobs
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
def start_scheduler():
@ -48,9 +55,8 @@ def enqueue_events_for_all_sites():
def enqueue_events_for_site(site):
def log_and_raise():
frappe.logger(__name__).error('Exception in Enqueue Events for Site {0}'.format(site) +
'\n' + frappe.get_traceback())
raise # pylint: disable=misplaced-bare-raise
error_message = 'Exception in Enqueue Events for Site {0}\n{1}'.format(site, frappe.get_traceback())
frappe.logger("scheduler").error(error_message)
try:
frappe.init(site=site)
@ -60,10 +66,10 @@ def enqueue_events_for_site(site):
enqueue_events(site=site)
frappe.logger(__name__).debug('Queued events for site {0}'.format(site))
frappe.logger("scheduler").debug('Queued events for site {0}'.format(site))
except frappe.db.OperationalError as e:
if frappe.db.is_access_denied(e):
frappe.logger(__name__).debug('Access denied for site {0}'.format(site))
frappe.logger("scheduler").debug('Access denied for site {0}'.format(site))
else:
log_and_raise()
except:

View file

@ -38,10 +38,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Website",
"modified": "2020-05-05 18:17:13.232473",
"modified": "2020-05-28 13:53:10.736212",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",
@ -51,7 +52,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"color": "",
"color": "#cef6d1",
"format": "{} Published",
"label": "Blog Post",
"link_to": "Blog Post",
@ -59,6 +60,7 @@
"type": "DocType"
},
{
"color": "#cef6d1",
"format": "{} Active",
"label": "Blogger",
"link_to": "Blogger",
@ -66,8 +68,11 @@
"type": "DocType"
},
{
"color": "#cef6d1",
"format": "{} Published",
"label": "Web Page",
"link_to": "Web Page",
"stats_filter": "{ \"published\": 1 }",
"type": "DocType"
},
{

View file

@ -2,9 +2,6 @@
// MIT License. See license.txt
frappe.ui.form.on('Web Page', {
onload: function() {
frappe.require('/assets/frappe/js/frappe/utils/web_page_block.js');
},
title: function(frm) {
if (frm.doc.title && !frm.doc.route) {
frm.set_value('route', frappe.scrub(frm.doc.title, '-'));
@ -49,6 +46,45 @@ frappe.ui.form.on('Web Page', {
}
});
frappe.ui.form.on("Web Page Block", {
edit_values(frm, cdt, cdn) {
let row = frm.selected_doc;
frappe.model.with_doc("Web Template", row.web_template).then((doc) => {
let d = new frappe.ui.Dialog({
title: __("Edit Values"),
fields: doc.fields.map((df) => {
if (df.fieldtype == "Section Break") {
df.collapsible = 1;
}
return df;
}),
primary_action(values) {
frappe.model.set_value(
cdt,
cdn,
"web_template_values",
JSON.stringify(values)
);
d.hide();
},
});
let values = JSON.parse(row.web_template_values || "{}");
d.set_values(values);
d.show();
d.sections.forEach((sect) => {
let fields_with_value = sect.fields_list.filter(
(field) => values[field.df.fieldname]
);
if (fields_with_value.length) {
sect.collapse(false);
}
});
});
},
});
frappe.tour['Web Page'] = [
{
fieldname: "title",
@ -102,4 +138,4 @@ frappe.tour['Web Page'] = [
title: __("Meta Image"),
description: __("The meta image is unique image representing the content of the page. Images for this Card should be at least 280px in width, and at least 150px in height.")
},
];
];

View file

@ -2,9 +2,6 @@
// MIT License. See license.txt
frappe.ui.form.on('Website Theme', {
onload: function() {
frappe.require('/assets/frappe/js/frappe/utils/web_page_block.js');
},
refresh(frm) {
frm.clear_custom_buttons();
frm.toggle_display(["module", "custom"], !frappe.boot.developer_mode);

View file

@ -10,7 +10,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website",
"idx": 0,
"is_complete": 0,
"modified": "2020-05-13 12:32:35.269025",
"modified": "2020-05-28 13:51:57.535269",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",

View file

@ -8,10 +8,12 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-04-30 19:06:10.750976",
"modified": "2020-05-28 13:51:53.157256",
"modified_by": "Administrator",
"name": "Add Blog Category",
"owner": "Administrator",
"reference_document": "Blog Category",
"title": "Add Blog Category"
"show_full_form": 0,
"title": "Add Blog Category",
"validate_action": 0
}

View file

@ -13,5 +13,7 @@
"name": "Create Blogger",
"owner": "Administrator",
"reference_document": "Blogger",
"title": "Create Blogger"
"show_full_form": 0,
"title": "Create Blogger",
"validate_action": 0
}

View file

@ -14,6 +14,8 @@
"name": "Enable Website Tracking",
"owner": "Administrator",
"reference_document": "Website Settings",
"show_full_form": 0,
"title": "Enable Website Tracking",
"validate_action": 0,
"value_to_validate": "1"
}

View file

@ -8,10 +8,12 @@
"is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-04-30 19:06:10.578218",
"modified": "2020-05-28 13:51:51.485924",
"modified_by": "Administrator",
"name": "Introduction to Website",
"owner": "Administrator",
"show_full_form": 0,
"title": "Introduction to Website",
"validate_action": 0,
"video_url": "https://www.youtube.com/watch?v=lyW6mfFBSNw"
}

View file

@ -8,10 +8,12 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-13 12:32:15.966570",
"modified": "2020-05-28 13:51:54.264456",
"modified_by": "Administrator",
"name": "Web Page Tour",
"owner": "Administrator",
"reference_document": "Web Page",
"title": "Learn about Web Pages"
"show_full_form": 0,
"title": "Learn about Web Pages",
"validate_action": 0
}

View file

@ -7,9 +7,9 @@
"custom_overrides": "",
"docstatus": 0,
"doctype": "Website Theme",
"font_properties": "wght:400;500;600;700;800",
"idx": 28,
"modified": "2020-05-19 00:33:58.011046",
"font_properties": "400,500,600,700,800",
"idx": 26,
"modified": "2020-05-20 14:47:12.938879",
"modified_by": "Administrator",
"module": "Website",
"name": "Standard",