diff --git a/frappe/commands/site.py b/frappe/commands/site.py old mode 100755 new mode 100644 index c5d2257d75..b54f369e34 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -1,7 +1,7 @@ # imports - standard imports import os -import sys import shutil +import sys # imports - third party imports import click @@ -65,11 +65,11 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None, "Restore site database from an sql file" from frappe.installer import ( _new_site, - extract_sql_from_archive, extract_files, + extract_sql_from_archive, is_downgrade, is_partial, - validate_database_sql + validate_database_sql, ) from frappe.utils.backups import Backup if not os.path.exists(sql_file_path): @@ -207,7 +207,7 @@ def restore(context, sql_file_path, encryption_key=None, db_root_username=None, @click.option('--encryption-key', help='Backup encryption key') @pass_context def partial_restore(context, sql_file_path, verbose, encryption_key=None): - from frappe.installer import partial_restore, extract_sql_from_archive + from frappe.installer import extract_sql_from_archive, partial_restore from frappe.utils.backups import Backup if not os.path.exists(sql_file_path): @@ -545,7 +545,7 @@ def _use(site, sites_path='.'): def use(site, sites_path='.'): if os.path.exists(os.path.join(sites_path, site)): - with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: + with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile: sitefile.write(site) print("Current Site set to {}".format(site)) else: @@ -751,6 +751,7 @@ def set_admin_password(context, admin_password=None, logout_all_sessions=False): def set_user_password(site, user, password, logout_all_sessions=False): import getpass + from frappe.utils.password import update_password try: @@ -881,15 +882,16 @@ def stop_recording(context): raise SiteNotSpecifiedError @click.command('ngrok') +@click.option('--bind-tls', is_flag=True, default=False, help='Returns a reference to the https tunnel.') @pass_context -def start_ngrok(context): +def start_ngrok(context, bind_tls): from pyngrok import ngrok site = get_site(context) frappe.init(site=site) port = frappe.conf.http_port or frappe.conf.webserver_port - tunnel = ngrok.connect(addr=str(port), host_header=site) + tunnel = ngrok.connect(addr=str(port), host_header=site, bind_tls=bind_tls) print(f'Public URL: {tunnel.public_url}') print('Inspect logs at http://localhost:4040') diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index f89f0d8765..475762f39d 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -18,6 +18,7 @@ from urllib.parse import unquote from frappe.utils.user import is_system_user from frappe.contacts.doctype.contact.contact import get_contact_name from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule +from parse import compile exclude_from_linked_with = True @@ -114,6 +115,44 @@ class Communication(Document, CommunicationEmailMixin): frappe.publish_realtime('new_message', self.as_dict(), user=self.reference_name, after_commit=True) + def set_signature_in_email_content(self): + """Set sender's User.email_signature or default outgoing's EmailAccount.signature to the email + """ + if not self.content: + return + + quill_parser = compile('
{signature}'
+
+ def before_save(self):
+ if not self.flags.skip_add_signature:
+ self.set_signature_in_email_content()
+
def on_update(self):
# add to _comment property of the doctype, so it shows up in
# comments count for the list view
diff --git a/frappe/core/doctype/communication/email.py b/frappe/core/doctype/communication/email.py
index 46ef7bf5d2..b51749ccb7 100755
--- a/frappe/core/doctype/communication/email.py
+++ b/frappe/core/doctype/communication/email.py
@@ -22,12 +22,30 @@ OUTGOING_EMAIL_ACCOUNT_MISSING = _("""
@frappe.whitelist()
-def make(doctype=None, name=None, content=None, subject=None, sent_or_received = "Sent",
- sender=None, sender_full_name=None, recipients=None, communication_medium="Email", send_email=False,
- print_html=None, print_format=None, attachments='[]', send_me_a_copy=False, cc=None, bcc=None,
- flags=None, read_receipt=None, print_letterhead=True, email_template=None, communication_type=None,
- ignore_permissions=False) -> Dict[str, str]:
- """Make a new communication.
+def make(
+ doctype=None,
+ name=None,
+ content=None,
+ subject=None,
+ sent_or_received="Sent",
+ sender=None,
+ sender_full_name=None,
+ recipients=None,
+ communication_medium="Email",
+ send_email=False,
+ print_html=None,
+ print_format=None,
+ attachments="[]",
+ send_me_a_copy=False,
+ cc=None,
+ bcc=None,
+ read_receipt=None,
+ print_letterhead=True,
+ email_template=None,
+ communication_type=None,
+ **kwargs,
+) -> Dict[str, str]:
+ """Make a new communication. Checks for email permissions for specified Document.
:param doctype: Reference DocType.
:param name: Reference Document name.
@@ -44,17 +62,71 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
:param send_me_a_copy: Send a copy to the sender (default **False**).
:param email_template: Template which is used to compose mail .
"""
- is_error_report = (doctype=="User" and name==frappe.session.user and subject=="Error Report")
- send_me_a_copy = cint(send_me_a_copy)
+ if kwargs:
+ from frappe.utils.commands import warn
+ warn(
+ f"Options {kwargs} used in frappe.core.doctype.communication.email.make "
+ "are deprecated or unsupported",
+ category=DeprecationWarning
+ )
+
+ if doctype and name and not frappe.has_permission(doctype=doctype, ptype="email", doc=name):
+ raise frappe.PermissionError(
+ f"You are not allowed to send emails related to: {doctype} {name}"
+ )
+
+ return _make(
+ doctype=doctype,
+ name=name,
+ content=content,
+ subject=subject,
+ sent_or_received=sent_or_received,
+ sender=sender,
+ sender_full_name=sender_full_name,
+ recipients=recipients,
+ communication_medium=communication_medium,
+ send_email=send_email,
+ print_html=print_html,
+ print_format=print_format,
+ attachments=attachments,
+ send_me_a_copy=cint(send_me_a_copy),
+ cc=cc,
+ bcc=bcc,
+ read_receipt=read_receipt,
+ print_letterhead=print_letterhead,
+ email_template=email_template,
+ communication_type=communication_type,
+ add_signature=False,
+ )
- if not ignore_permissions:
- if doctype and name and not is_error_report and not frappe.has_permission(doctype, "email", name) and not (flags or {}).get('ignore_doctype_permissions'):
- raise frappe.PermissionError("You are not allowed to send emails related to: {doctype} {name}".format(
- doctype=doctype, name=name))
- if not sender:
- sender = get_formatted_email(frappe.session.user)
+def _make(
+ doctype=None,
+ name=None,
+ content=None,
+ subject=None,
+ sent_or_received="Sent",
+ sender=None,
+ sender_full_name=None,
+ recipients=None,
+ communication_medium="Email",
+ send_email=False,
+ print_html=None,
+ print_format=None,
+ attachments="[]",
+ send_me_a_copy=False,
+ cc=None,
+ bcc=None,
+ read_receipt=None,
+ print_letterhead=True,
+ email_template=None,
+ communication_type=None,
+ add_signature=True,
+) -> Dict[str, str]:
+ """Internal method to make a new communication that ignores Permission checks.
+ """
+ sender = sender or get_formatted_email(frappe.session.user)
recipients = list_to_str(recipients) if isinstance(recipients, list) else recipients
cc = list_to_str(cc) if isinstance(cc, list) else cc
bcc = list_to_str(bcc) if isinstance(bcc, list) else bcc
@@ -77,7 +149,9 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
"read_receipt":read_receipt,
"has_attachment": 1 if attachments else 0,
"communication_type": communication_type,
- }).insert(ignore_permissions=True)
+ })
+ comm.flags.skip_add_signature = not add_signature
+ comm.insert(ignore_permissions=True)
# if not committed, delayed task doesn't find the communication
if attachments:
@@ -87,17 +161,21 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
if cint(send_email):
if not comm.get_outgoing_email_account():
- frappe.throw(msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError)
+ frappe.throw(
+ msg=OUTGOING_EMAIL_ACCOUNT_MISSING, exc=frappe.OutgoingEmailError
+ )
- comm.send_email(print_html=print_html, print_format=print_format,
- send_me_a_copy=send_me_a_copy, print_letterhead=print_letterhead)
+ comm.send_email(
+ print_html=print_html,
+ print_format=print_format,
+ send_me_a_copy=send_me_a_copy,
+ print_letterhead=print_letterhead,
+ )
emails_not_sent_to = comm.exclude_emails_list(include_sender=send_me_a_copy)
- return {
- "name": comm.name,
- "emails_not_sent_to": ", ".join(emails_not_sent_to)
- }
+ return {"name": comm.name, "emails_not_sent_to": ", ".join(emails_not_sent_to)}
+
def validate_email(doc: "Communication") -> None:
"""Validate Email Addresses of Recipients and CC"""
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index a47f539466..9e9529cd5e 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -668,8 +668,7 @@
"link_fieldname": "user"
}
],
- "max_attachments": 5,
- "modified": "2022-01-03 11:53:25.250822",
+ "modified": "2022-03-09 01:47:56.745069",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py
index 572d3f2a94..010d65c95b 100644
--- a/frappe/desk/form/linked_with.py
+++ b/frappe/desk/form/linked_with.py
@@ -1,9 +1,10 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
+
import json
from collections import defaultdict
import itertools
-from typing import List
+from typing import Dict, List, Optional
import frappe
import frappe.desk.form.load
@@ -367,7 +368,7 @@ def get_exempted_doctypes():
@frappe.whitelist()
-def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
+def get_linked_docs(doctype: str, name: str, linkinfo: Optional[Dict] = None) -> Dict[str, List]:
if isinstance(linkinfo, str):
# additional fields are added in linkinfo
linkinfo = json.loads(linkinfo)
@@ -377,23 +378,21 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
if not linkinfo:
return results
- if for_doctype:
- links = frappe.get_doc(doctype, name).get_link_filters(for_doctype)
-
- if links:
- linkinfo = links
-
- if for_doctype in linkinfo:
- # only get linked with for this particular doctype
- linkinfo = { for_doctype: linkinfo.get(for_doctype) }
- else:
- return results
-
for dt, link in linkinfo.items():
filters = []
link["doctype"] = dt
- link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
+ try:
+ link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
+ except Exception as e:
+ if isinstance(e, frappe.DoesNotExistError):
+ if frappe.local.message_log:
+ frappe.local.message_log.pop()
+ continue
linkmeta = link_meta_bundle[0]
+
+ if not linkmeta.has_permission():
+ continue
+
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {
"in_list_view": 1,
@@ -456,6 +455,13 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
return results
+
+@frappe.whitelist()
+def get(doctype, docname):
+ linked_doctypes = get_linked_doctypes(doctype=doctype)
+ return get_linked_docs(doctype=doctype, name=docname, linkinfo=linked_doctypes)
+
+
@frappe.whitelist()
def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
"""add list of doctypes this doctype is 'linked' with.
@@ -470,6 +476,7 @@ def get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
else:
return frappe.cache().hget("linked_doctypes", doctype, lambda: _get_linked_doctypes(doctype))
+
def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False):
ret = {}
# find fields where this doctype is linked
@@ -499,6 +506,7 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False)
return ret
+
def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
filters = [['fieldtype','=', 'Link'], ['options', '=', doctype]]
@@ -529,6 +537,7 @@ def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
return ret
+
def get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
ret = {}
diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json
index baabd4991e..b42f4755cb 100644
--- a/frappe/email/doctype/newsletter/newsletter.json
+++ b/frappe/email/doctype/newsletter/newsletter.json
@@ -236,8 +236,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
- "max_attachments": 3,
- "modified": "2021-12-06 20:09:37.963141",
+ "modified": "2022-03-09 01:48:16.741603",
"modified_by": "Administrator",
"module": "Email",
"name": "Newsletter",
diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py
index 2b62530847..bad32fb68f 100644
--- a/frappe/email/doctype/notification/notification.py
+++ b/frappe/email/doctype/notification/notification.py
@@ -186,7 +186,7 @@ def get_context(context):
def send_an_email(self, doc, context):
from email.utils import formataddr
- from frappe.core.doctype.communication.email import make as make_communication
+ from frappe.core.doctype.communication.email import _make as make_communication
subject = self.subject
if "{" in subject:
subject = frappe.render_template(self.subject, context)
@@ -216,7 +216,8 @@ def get_context(context):
# Add mail notification to communication list
# No need to add if it is already a communication.
if doc.doctype != 'Communication':
- make_communication(doctype=doc.doctype,
+ make_communication(
+ doctype=doc.doctype,
name=doc.name,
content=message,
subject=subject,
@@ -228,7 +229,7 @@ def get_context(context):
cc=cc,
bcc=bcc,
communication_type='Automated Message',
- ignore_permissions=True)
+ )
def send_a_slack_msg(self, doc, context):
send_slack_message(
diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py
index c25e996bd3..0f45e42aac 100755
--- a/frappe/email/email_body.py
+++ b/frappe/email/email_body.py
@@ -259,17 +259,12 @@ def get_formatted_html(subject, message, footer=None, print_html=None,
email_account = email_account or EmailAccount.find_outgoing(match_by_email=sender)
- signature = None
- if "" not in message:
- signature = get_signature(email_account)
-
rendered_email = frappe.get_template("templates/emails/standard.html").render({
"brand_logo": get_brand_logo(email_account) if with_container or header else None,
"with_container": with_container,
"site_url": get_url(),
"header": get_header(header),
"content": message,
- "signature": signature,
"footer": get_footer(email_account, footer),
"title": subject,
"print_html": print_html,
@@ -281,8 +276,7 @@ def get_formatted_html(subject, message, footer=None, print_html=None,
if unsubscribe_link:
html = html.replace("", unsubscribe_link.html)
- html = inline_style_in_html(html)
- return html
+ return inline_style_in_html(html)
@frappe.whitelist()
def get_email_html(template, args, subject, header=None, with_container=False):
diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py
index faa3859c91..b4a53e3131 100644
--- a/frappe/model/rename_doc.py
+++ b/frappe/model/rename_doc.py
@@ -43,8 +43,8 @@ def update_document_title(
title_field = doc.meta.get_title_field()
- title_updated = (title_field != "name") and (updated_title != doc.get(title_field))
- name_updated = updated_name != doc.name
+ title_updated = updated_title and (title_field != "name") and (updated_title != doc.get(title_field))
+ name_updated = updated_name and (updated_name != doc.name)
if name_updated:
docname = rename_doc(doctype=doctype, old=docname, new=updated_name, merge=merge)
diff --git a/frappe/public/js/frappe/form/linked_with.js b/frappe/public/js/frappe/form/linked_with.js
index 20db7bdb7c..c47a6e0c86 100644
--- a/frappe/public/js/frappe/form/linked_with.js
+++ b/frappe/public/js/frappe/form/linked_with.js
@@ -1,9 +1,8 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// MIT License. See license.txt
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+// MIT License. See LICENSE
frappe.ui.form.LinkedWith = class LinkedWith {
-
constructor(opts) {
$.extend(this, opts);
}
@@ -21,29 +20,23 @@ frappe.ui.form.LinkedWith = class LinkedWith {
}
make_dialog() {
-
this.dialog = new frappe.ui.Dialog({
title: __("Linked With")
});
this.dialog.on_page_show = () => {
- // execute ajax calls sequentially
- // 1. get linked doctypes
- // 2. load all doctypes
- // 3. load linked docs
- this.get_linked_doctypes()
- .then(() => this.load_doctypes())
- .then(() => this.links_not_permitted_or_missing())
- .then(() => this.get_linked_docs())
- .then(() => this.make_html());
+ frappe.xcall(
+ "frappe.desk.form.linked_with.get",
+ {"doctype": cur_frm.doctype, "docname": cur_frm.docname},
+ ).then(r => {
+ this.frm.__linked_docs = r;
+ }).then(() => this.make_html());
};
}
make_html() {
- const linked_docs = this.frm.__linked_docs;
-
let html = '';
-
+ const linked_docs = this.frm.__linked_docs;
const linked_doctypes = Object.keys(linked_docs);
if (linked_doctypes.length === 0) {
@@ -63,88 +56,6 @@ frappe.ui.form.LinkedWith = class LinkedWith {
$(this.dialog.body).html(html);
}
- load_doctypes() {
- const already_loaded = Object.keys(locals.DocType);
- let doctypes_to_load = [];
-
- if (this.frm.__linked_doctypes) {
- doctypes_to_load =
- Object.keys(this.frm.__linked_doctypes)
- .filter(doctype => !already_loaded.includes(doctype));
- }
-
- // load all doctypes asynchronously using with_doctype
- const promises = doctypes_to_load.map(dt => {
- return frappe.model.with_doctype(dt, () => {
- if(frappe.listview_settings[dt]) {
- // add additional fields to __linked_doctypes
- this.frm.__linked_doctypes[dt].add_fields =
- frappe.listview_settings[dt].add_fields;
- }
- });
- });
-
- return Promise.all(promises);
- }
-
- links_not_permitted_or_missing() {
- let links = null;
-
- if (this.frm.__linked_doctypes) {
- links =
- Object.keys(this.frm.__linked_doctypes)
- .filter(frappe.model.can_get_report);
- }
-
- let flag;
- if(!links) {
- $(this.dialog.body).html(`${this.frm.__linked_doctypes
- ? __("Not enough permission to see links")
- : __("Not Linked to any record")}`);
- flag = true;
- }
- flag = false;
-
- // reject Promise if not_permitted or missing
- return new Promise(
- (resolve, reject) => flag ? reject() : resolve()
- );
- }
-
- get_linked_doctypes() {
- return new Promise((resolve) => {
- if (this.frm.__linked_doctypes) {
- resolve();
- }
-
- frappe.call({
- method: "frappe.desk.form.linked_with.get_linked_doctypes",
- args: {
- doctype: this.frm.doctype
- },
- callback: (r) => {
- this.frm.__linked_doctypes = r.message;
- resolve();
- }
- });
- });
- }
-
- get_linked_docs() {
- return frappe.call({
- method: "frappe.desk.form.linked_with.get_linked_docs",
- args: {
- doctype: this.frm.doctype,
- name: this.frm.docname,
- linkinfo: this.frm.__linked_doctypes,
- for_doctype: this.for_doctype
- },
- callback: (r) => {
- this.frm.__linked_docs = r.message || {};
- }
- });
- }
-
make_doc_head(heading) {
return `
{{ content }} {{ signature }}
diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json
index b05293f28b..5e3cc78d70 100644
--- a/frappe/website/doctype/blog_post/blog_post.json
+++ b/frappe/website/doctype/blog_post/blog_post.json
@@ -213,8 +213,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
- "max_attachments": 5,
- "modified": "2021-11-23 10:42:01.759723",
+ "modified": "2022-03-09 01:48:25.227295",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json
index b1fdd02af7..e7bd705272 100644
--- a/frappe/website/doctype/web_page/web_page.json
+++ b/frappe/website/doctype/web_page/web_page.json
@@ -338,8 +338,7 @@
"index_web_pages_for_search": 1,
"is_published_field": "published",
"links": [],
- "max_attachments": 20,
- "modified": "2022-01-03 13:01:48.182645",
+ "modified": "2022-03-09 01:45:28.548671",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",
diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json
index 3b199a4b58..b628437315 100644
--- a/frappe/website/doctype/website_settings/website_settings.json
+++ b/frappe/website/doctype/website_settings/website_settings.json
@@ -420,8 +420,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "max_attachments": 10,
- "modified": "2022-02-24 15:37:22.360138",
+ "modified": "2022-03-09 01:47:31.094462",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Settings",
diff --git a/requirements.txt b/requirements.txt
index ba4a1a598b..c77ab1d424 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -29,6 +29,7 @@ maxminddb-geolite2==2018.703
num2words~=0.5.10
oauthlib~=3.1.0
openpyxl~=3.0.7
+parse~=1.19.0
passlib~=1.7.4
paytmchecksum~=1.7.0
pdfkit~=0.6.1