Merge branch 'frappe:develop' into multiple_imap_folder

This commit is contained in:
Manuel 2021-11-24 15:36:58 +01:00 committed by GitHub
commit 2eb5fb9230
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 270 additions and 9 deletions

View file

@ -10,6 +10,13 @@ frappe.ui.form.on('Server Script', {
frm.dashboard.hide();
}
if (!frm.is_new()) {
frm.add_custom_button(__('Compare Versions'), () => {
new frappe.ui.DiffView("Server Script", "script", frm.doc.name);
});
}
frm.call('get_autocompletion_items')
.then(r => r.message)
.then(items => {

View file

@ -43,6 +43,12 @@ frappe.ui.form.on('Client Script', {
d.show();
});
});
if (!frm.is_new()) {
frm.add_custom_button(__('Compare Versions'), () => {
new frappe.ui.DiffView("Client Script", "script", frm.doc.name);
});
}
}
frm.set_query('dt', {

View file

@ -11,6 +11,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils.user import get_enabled_system_users
from frappe.desk.reportview import get_filters_cond
from frappe.desk.doctype.notification_settings.notification_settings import is_email_notifications_enabled_for_type
weekdays = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
communication_mapping = {"": "Event", "Event": "Event", "Meeting": "Meeting", "Call": "Phone", "Sent/Received Email": "Email", "Other": "Other"}
@ -141,7 +142,12 @@ def has_permission(doc, user):
def send_event_digest():
today = nowdate()
for user in get_enabled_system_users():
# select only those users that have event reminder email notifications enabled
users = [user for user in get_enabled_system_users() if
is_email_notifications_enabled_for_type(user.name, 'Event Reminders')]
for user in users:
events = get_events(today, today, user.name, for_reminder=True)
if events:
frappe.set_user_lang(user.name, user.language)

View file

@ -14,6 +14,7 @@
"enable_email_assignment",
"enable_email_energy_point",
"enable_email_share",
"enable_email_event_reminders",
"user",
"seen",
"system_notifications_section",
@ -97,12 +98,19 @@
"fieldname": "energy_points_system_notifications",
"fieldtype": "Check",
"label": "Energy Points"
},
{
"default": "1",
"depends_on": "enable_email_notifications",
"fieldname": "enable_email_event_reminders",
"fieldtype": "Check",
"label": "Event Reminders"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-11-16 12:18:46.955501",
"modified": "2021-11-24 14:45:31.931154",
"modified_by": "Administrator",
"module": "Desk",
"name": "Notification Settings",
@ -125,4 +133,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View file

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies and Contributors
# See license.txt
# import frappe
import unittest
class TestNotificationSettings(unittest.TestCase):
pass

6
frappe/email/doctype/newsletter/newsletter.py Executable file → Normal file
View file

@ -159,11 +159,10 @@ class Newsletter(WebsiteGenerator):
def send_newsletter(self, emails: List[str]):
"""Trigger email generation for `emails` and add it in Email Queue.
"""
# TODO: get rid of this maybe?
message = self.get_message()
attachments = self.get_newsletter_attachments()
sender = self.send_from or frappe.utils.get_formatted_email(self.owner)
args = {"message": message, "name": self.name}
args = self.as_dict()
args["message"] = self.get_message()
is_auto_commit_set = bool(frappe.db.auto_commit_on_many_writes)
frappe.db.auto_commit_on_many_writes = not frappe.flags.in_test
@ -172,7 +171,6 @@ class Newsletter(WebsiteGenerator):
subject=self.subject,
sender=sender,
recipients=emails,
message=message,
attachments=attachments,
template="newsletter",
add_unsubscribe_link=self.send_unsubscribe_link,

View file

@ -62,6 +62,7 @@ import "./frappe/utils/help_links.js";
import "./frappe/utils/address_and_contact.js";
import "./frappe/utils/preview_email.js";
import "./frappe/utils/file_manager.js";
import "./frappe/utils/diffview";
import "./frappe/upload.js";
import "./frappe/ui/tree.js";

View file

@ -0,0 +1,100 @@
frappe.provide("frappe.ui");
frappe.ui.DiffView = class DiffView {
constructor(doctype, fieldname, docname) {
this.dialog = null;
this.handler = null;
this.doctype = doctype;
this.fieldname = fieldname;
this.docname = docname;
this.dialog = this.make_dialog();
this.set_empty_state();
this.dialog.show();
}
make_dialog() {
const get_query = () => ({
query: "frappe.utils.diff.version_query",
filters: { docname: this.docname, ref_doctype: this.doctype },
});
const onchange = () => this.compute_diff();
let dialog = new frappe.ui.Dialog({
title: __("Compare Versions"),
fields: [
{
label: __("From version"),
fieldtype: "Link",
fieldname: "from_version",
options: "Version",
reqd: 1,
get_query,
onchange,
},
{
fieldtype: "Column Break",
fieldname: "cb",
},
{
label: __("To version"),
fieldtype: "Link",
fieldname: "to_version",
options: "Version",
reqd: 1,
get_query,
onchange,
},
{
fieldtype: "Section Break",
fieldname: "sb",
},
{
label: __("Diff"),
fieldtype: "HTML",
fieldname: "diff",
},
],
size: "large",
});
return dialog;
}
compute_diff() {
const from_version = this.dialog.get_value("from_version");
const to_version = this.dialog.get_value("to_version");
const fieldname = this.fieldname;
if (from_version && to_version) {
frappe
.xcall("frappe.utils.diff.get_version_diff", {
from_version,
to_version,
fieldname,
})
.then((data) => {
this.dialog.set_value("diff", this.prettify_diff(data));
});
} else {
this.set_empty_state();
}
}
prettify_diff(diff) {
let html = ``;
diff.forEach((line) => {
let line_class = "";
if (line.startsWith("+")) {
line_class = "insert";
} else if (line.startsWith("-")) {
line_class = "delete";
}
html += `<div class=${line_class}>${line}</div>`;
});
return `<div class='diffview'>${html}</div>`;
}
set_empty_state() {
this.dialog.set_value("diff", __("Select two versions to view the diff."));
}
};

View file

@ -388,6 +388,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.$charts_wrapper.addClass('hidden');
this.save_view_user_settings(
{ chart_args: null });
this.chart_args = null;
}
}

View file

@ -574,6 +574,31 @@ details > summary:focus {
}
}
.diffview {
font-family: monospace;
white-space: pre;
word-wrap: break-word;
color: var(--text-color);
}
.diffview .insert {
background-color: var(--green-100);
}
.diffview .delete {
background-color: var(--red-100);
}
[data-theme="dark"] {
.diffview .insert {
background-color: var(--green-800);
}
.diffview .delete {
background-color: var(--red-800);
}
}
// REDESIGN TODO: Handling of broken images?
// img.no-image:before {
// .img-background();
@ -603,4 +628,4 @@ details > summary:focus {
.chart-container {
direction: ltr;
}
*/
*/

View file

@ -3,8 +3,11 @@
{{ message }}
</div>
</div>
{% if published and send_webview_link %}
<div style="font-size: 12px; line-height: 20px;">
<div>
Open in <a style="color: #687178; text-decoration: underline;" href="/newsletters/{{ name }}" target="_blank">web</a>
</div>
</div>
</div>
{% endif %}

View file

@ -7,6 +7,7 @@ from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url
from frappe.utils import validate_url, validate_email_address
from frappe.utils import ceil, floor
from frappe.utils.data import cast, validate_python_code
from frappe.utils.diff import get_version_diff, version_query, _get_value_from_version
from PIL import Image
from frappe.utils.image import strip_exif_data, optimize_image
@ -269,3 +270,39 @@ class TestPythonExpressions(unittest.TestCase):
]
for expr in invalid_expressions:
self.assertRaises(frappe.ValidationError, validate_python_code, expr)
class TestDiffUtils(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.doc = frappe.get_doc(doctype="Client Script", dt="Client Script")
cls.doc.save(ignore_version=False)
cls.doc.script = "2;"
cls.doc.save(ignore_version=False)
cls.doc.script = "42;"
cls.doc.save(ignore_version=False)
cls.versions = version_query(doctype="Version", txt="", searchfield="name", start=0,
page_len=20, filters={"ref_doctype": cls.doc.doctype, "docname": cls.doc.name})
@classmethod
def tearDownClass(cls):
cls.doc.delete()
def test_version_query(self):
self.assertGreaterEqual(len(self.versions), 2)
def test_get_field_value_from_version(self):
latest_version = self.versions[0][0]
self.assertEqual("42;", _get_value_from_version(latest_version, fieldname="script")[0])
old_version = self.versions[1][0]
self.assertEqual("2;", _get_value_from_version(old_version, fieldname="script")[0])
def test_get_version_diff(self):
old_version = self.versions[1][0]
latest_version = self.versions[0][0]
diff = get_version_diff(old_version, latest_version)
self.assertIn('-2;', diff)
self.assertIn('+42;', diff)

61
frappe/utils/diff.py Normal file
View file

@ -0,0 +1,61 @@
import json
from difflib import unified_diff
from typing import List
import frappe
from frappe.utils import pretty_date
@frappe.whitelist()
def get_version_diff(
from_version: str, to_version: str, fieldname: str = "script"
) -> List[str]:
before, before_timestamp = _get_value_from_version(from_version, fieldname)
after, after_timestamp = _get_value_from_version(to_version, fieldname)
if not (before and after):
return ["Values not available for diff"]
before = before.split("\n")
after = after.split("\n")
diff = unified_diff(
before,
after,
fromfile=from_version,
tofile=to_version,
fromfiledate=before_timestamp,
tofiledate=after_timestamp,
)
return list(diff)
def _get_value_from_version(version_name: str, fieldname: str):
version = frappe.get_list(
"Version", fields=["data", "modified"], filters={"name": version_name}
)
if version:
data = json.loads(version[0].data)
changed_fields = data.get("changed", [])
# data structure of field: [fieldname, before_save, after_save]
for field in changed_fields:
if field[0] == fieldname:
return field[2], str(version[0].modified)
return None, None
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def version_query(doctype, txt, searchfield, start, page_len, filters):
results = frappe.get_list(
"Version",
fields=["name", "modified"],
filters=filters,
limit_start=start,
limit_page_length=page_len,
order_by="modified desc",
)
return [(d.name, pretty_date(d.modified), d.modified) for d in results]