diff --git a/.eslintrc b/.eslintrc
index eef33ec8a0..69c731b079 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -78,6 +78,7 @@
"has_common": true,
"has_words": true,
"validate_email": true,
+ "validate_name": true,
"validate_phone": true,
"get_number_format": true,
"format_number": true,
diff --git a/frappe/automation/desk_page/tools/tools.json b/frappe/automation/desk_page/tools/tools.json
index 235498724d..2164a4ce38 100644
--- a/frappe/automation/desk_page/tools/tools.json
+++ b/frappe/automation/desk_page/tools/tools.json
@@ -3,7 +3,7 @@
{
"hidden": 0,
"label": "Tools",
- "links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
+ "links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
},
{
"hidden": 0,
@@ -32,7 +32,7 @@
"idx": 0,
"is_standard": 1,
"label": "Tools",
- "modified": "2020-04-01 11:24:40.804346",
+ "modified": "2020-04-20 18:21:14.152537",
"modified_by": "Administrator",
"module": "Automation",
"name": "Tools",
diff --git a/frappe/boot.py b/frappe/boot.py
index eed434f870..9d5dbe1909 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -17,6 +17,7 @@ from frappe.utils.change_log import get_versions
from frappe.translate import get_lang_dict
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.social.doctype.post.post import frequently_visited_links
@@ -79,6 +80,7 @@ def get_bootinfo():
bootinfo.success_action = get_success_action()
bootinfo.update(get_email_accounts(user=frappe.session.user))
bootinfo.energy_points_enabled = is_energy_point_enabled()
+ bootinfo.website_tracking_enabled = is_tracking_enabled()
bootinfo.points = get_energy_points(frappe.session.user)
bootinfo.frequently_visited_links = frequently_visited_links()
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index f7c9cbe28a..d922cfe166 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -477,7 +477,8 @@ class DocType(Document):
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
if field_dict:
new_field_dicts.append(field_dict[0])
- remaining_field_names.remove(fieldname)
+ if fieldname in remaining_field_names:
+ remaining_field_names.remove(fieldname)
for fieldname in remaining_field_names:
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict['fields']))
@@ -498,7 +499,8 @@ class DocType(Document):
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
if field_dict:
new_field_dicts.append(field_dict[0])
- remaining_field_names.remove(fieldname)
+ if fieldname in remaining_field_names:
+ remaining_field_names.remove(fieldname)
for fieldname in remaining_field_names:
field_dict = list(filter(lambda d: d['fieldname'] == fieldname, docdict.get('fields', [])))
@@ -893,7 +895,7 @@ def validate_fields(meta):
field.fetch_from = field.fetch_from.strip('\n').strip()
def validate_data_field_type(docfield):
- if docfield.fieldtype == "Data":
+ if docfield.fieldtype == "Data" and not (docfield.oldfieldtype and docfield.oldfieldtype != "Data"):
if docfield.options and (docfield.options not in data_field_options):
df_str = frappe.bold(_(docfield.label))
text_str = _("{0} is an invalid Data field.").format(df_str) + "
" * 2 + _("Only Options allowed for Data field are:") + "
"
diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js
index b17548d994..b8e16bfe25 100644
--- a/frappe/core/doctype/user/user.js
+++ b/frappe/core/doctype/user/user.js
@@ -97,47 +97,49 @@ frappe.ui.form.on('User', {
});
}, __("Password"));
- frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
- if (value === 1 && frm.doc.name != "Administrator") {
- frm.add_custom_button(__("Reset LDAP Password"), function() {
- const d = new frappe.ui.Dialog({
- title: __("Reset LDAP Password"),
- fields: [
- {
- label: __("New Password"),
- fieldtype: "Password",
- fieldname: "new_password",
- reqd: 1
- },
- {
- label: __("Confirm New Password"),
- fieldtype: "Password",
- fieldname: "confirm_password",
- reqd: 1
- },
- {
- label: __("Logout All Sessions"),
- fieldtype: "Check",
- fieldname: "logout_sessions"
+ if (frappe.user.has_role("System Manager")) {
+ frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
+ if (value === 1 && frm.doc.name != "Administrator") {
+ frm.add_custom_button(__("Reset LDAP Password"), function() {
+ const d = new frappe.ui.Dialog({
+ title: __("Reset LDAP Password"),
+ fields: [
+ {
+ label: __("New Password"),
+ fieldtype: "Password",
+ fieldname: "new_password",
+ reqd: 1
+ },
+ {
+ label: __("Confirm New Password"),
+ fieldtype: "Password",
+ fieldname: "confirm_password",
+ reqd: 1
+ },
+ {
+ label: __("Logout All Sessions"),
+ fieldtype: "Check",
+ fieldname: "logout_sessions"
+ }
+ ],
+ primary_action: (values) => {
+ d.hide();
+ if (values.new_password !== values.confirm_password) {
+ frappe.throw(__("Passwords do not match!"));
+ }
+ frappe.call(
+ "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
+ user: frm.doc.email,
+ password: values.new_password,
+ logout: values.logout_sessions
+ });
}
- ],
- primary_action: (values) => {
- d.hide();
- if (values.new_password !== values.confirm_password) {
- frappe.throw(__("Passwords do not match!"));
- }
- frappe.call(
- "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
- user: frm.doc.email,
- password: values.new_password,
- logout: values.logout_sessions
- });
- }
- });
- d.show();
- }, __("Password"));
- }
- });
+ });
+ d.show();
+ }, __("Password"));
+ }
+ });
+ }
frm.add_custom_button(__("Reset OTP Secret"), function() {
frappe.call({
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 7837c90d2b..8370af6808 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -551,6 +551,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
res = _get_user_for_update_password(key, old_password)
if res.get('message'):
+ frappe.local.response.http_status_code = 410
return res['message']
else:
user = res['user']
@@ -718,7 +719,7 @@ def _get_user_for_update_password(key, old_password):
user = frappe.db.get_value("User", {"reset_password_key": key})
if not user:
return {
- 'message': _("Cannot Update: Incorrect / Expired Link.")
+ 'message': _("The Link specified has either been used before or Invalid")
}
elif old_password:
diff --git a/frappe/core/doctype/video/__init__.py b/frappe/core/doctype/video/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/video/test_video.py b/frappe/core/doctype/video/test_video.py
new file mode 100644
index 0000000000..0bed1e98d6
--- /dev/null
+++ b/frappe/core/doctype/video/test_video.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestVideo(unittest.TestCase):
+ pass
diff --git a/frappe/core/doctype/video/video.js b/frappe/core/doctype/video/video.js
new file mode 100644
index 0000000000..36ea240a36
--- /dev/null
+++ b/frappe/core/doctype/video/video.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Video', {
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/frappe/core/doctype/video/video.json b/frappe/core/doctype/video/video.json
new file mode 100644
index 0000000000..26a407c05c
--- /dev/null
+++ b/frappe/core/doctype/video/video.json
@@ -0,0 +1,106 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:title",
+ "creation": "2018-10-17 05:47:13.087395",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "provider",
+ "url",
+ "column_break_4",
+ "publish_date",
+ "duration",
+ "section_break_7",
+ "description"
+ ],
+ "fields": [
+ {
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "unique": 1
+ },
+ {
+ "fieldname": "provider",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Provider",
+ "options": "YouTube\nVimeo",
+ "reqd": 1
+ },
+ {
+ "fieldname": "url",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "URL",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "publish_date",
+ "fieldtype": "Date",
+ "label": "Publish Date"
+ },
+ {
+ "fieldname": "duration",
+ "fieldtype": "Data",
+ "label": "Duration"
+ },
+ {
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Text Editor",
+ "in_list_view": 1,
+ "label": "Description",
+ "reqd": 1
+ }
+ ],
+ "links": [],
+ "modified": "2020-04-22 12:09:49.057403",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Video",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "if_owner": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "All",
+ "share": 1,
+ "write": 1
+ },
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/video/video.py b/frappe/core/doctype/video/video.py
new file mode 100644
index 0000000000..fdbd3a1abe
--- /dev/null
+++ b/frappe/core/doctype/video/video.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class Video(Document):
+ pass
diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py
index 3a8815ca71..109dd25f4f 100644
--- a/frappe/desk/notifications.py
+++ b/frappe/desk/notifications.py
@@ -268,8 +268,9 @@ def get_open_count(doctype, name, items=[]):
"count": out,
}
- module = frappe.get_meta_module(doctype)
- if hasattr(module, "get_timeline_data"):
- out["timeline_data"] = module.get_timeline_data(doctype, name)
+ if not meta.custom:
+ module = frappe.get_meta_module(doctype)
+ if hasattr(module, "get_timeline_data"):
+ out["timeline_data"] = module.get_timeline_data(doctype, name)
return out
diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py
index f994dc0d30..164f6389eb 100644
--- a/frappe/desk/query_report.py
+++ b/frappe/desk/query_report.py
@@ -242,7 +242,7 @@ def get_prepared_report_result(report, filters, dn="", user=None):
columns = json.loads(doc.columns) if doc.columns else data[0]
for column in columns:
- if isinstance(column, dict):
+ if isinstance(column, dict) and column.get("label"):
column["label"] = _(column["label"])
latest_report_data = {
diff --git a/frappe/email/doctype/email_domain/email_domain.py b/frappe/email/doctype/email_domain/email_domain.py
index b6585d966b..08583dc228 100644
--- a/frappe/email/doctype/email_domain/email_domain.py
+++ b/frappe/email/doctype/email_domain/email_domain.py
@@ -39,7 +39,7 @@ class EmailDomain(Document):
except Exception:
frappe.throw(_("Incoming email account not correct"))
- return None
+
finally:
try:
if self.use_imap:
@@ -48,9 +48,10 @@ class EmailDomain(Document):
test.quit()
except Exception:
pass
+
try:
- if self.use_ssl_for_outgoing:
- if not self.smtp_port:
+ if self.get('use_ssl_for_outgoing'):
+ if not self.get('smtp_port'):
self.smtp_port = 465
sess = smtplib.SMTP_SSL((self.smtp_server or "").encode('utf-8'),
@@ -62,28 +63,15 @@ class EmailDomain(Document):
sess.quit()
except Exception:
frappe.throw(_("Outgoing email account not correct"))
- return None
- return
def on_update(self):
"""update all email accounts using this domain"""
- for email_account in frappe.get_all("Email Account",
- filters={"domain": self.name}):
-
+ for email_account in frappe.get_all("Email Account", filters={"domain": self.name}):
try:
- email_account = frappe.get_doc("Email Account",
- email_account.name)
- email_account.set("email_server",self.email_server)
- email_account.set("use_imap",self.use_imap)
- email_account.set("use_ssl",self.use_ssl)
- email_account.set("use_tls",self.use_tls)
- email_account.set("attachment_limit",self.attachment_limit)
- email_account.set("smtp_server",self.smtp_server)
- email_account.set("smtp_port",self.smtp_port)
- email_account.set("use_ssl_for_outgoing", self.use_ssl_for_outgoing)
- email_account.set("append_emails_to_sent_folder", self.append_emails_to_sent_folder)
+ email_account = frappe.get_doc("Email Account", email_account.name)
+ for attr in ["email_server", "use_imap", "use_ssl", "use_tls", "attachment_limit", "smtp_server", "smtp_port", "use_ssl_for_outgoing", "append_emails_to_sent_folder"]:
+ email_account.set(attr, self.get(attr, default=0))
email_account.save()
+
except Exception as e:
- frappe.msgprint(email_account.name)
- frappe.throw(e)
- return None
+ frappe.msgprint(_("Error has occurred in {0}").format(email_account.name), raise_exception=e.__class__)
diff --git a/frappe/exceptions.py b/frappe/exceptions.py
index ef75a36e03..9a1c1fb0b0 100644
--- a/frappe/exceptions.py
+++ b/frappe/exceptions.py
@@ -78,6 +78,7 @@ class TimestampMismatchError(ValidationError): pass
class EmptyTableError(ValidationError): pass
class LinkExistsError(ValidationError): pass
class InvalidEmailAddressError(ValidationError): pass
+class InvalidNameError(ValidationError): pass
class InvalidPhoneNumberError(ValidationError): pass
class TemplateNotFoundError(ValidationError): pass
class UniqueValidationError(ValidationError): pass
@@ -95,4 +96,4 @@ class DataTooLongException(ValidationError): pass
# OAuth exceptions
class InvalidAuthorizationHeader(CSRFTokenError): pass
class InvalidAuthorizationPrefix(CSRFTokenError): pass
-class InvalidAuthorizationToken(CSRFTokenError): pass
\ No newline at end of file
+class InvalidAuthorizationToken(CSRFTokenError): pass
diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py
index 7af987f4bc..93ef78df7b 100644
--- a/frappe/model/__init__.py
+++ b/frappe/model/__init__.py
@@ -48,7 +48,7 @@ table_fields = ('Table', 'Table MultiSelect')
core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'DocType Action', 'DocType Link', 'User', 'Role', 'Has Role',
'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form',
'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script')
-data_field_options = ('Email', 'Phone')
+data_field_options = ('Email', 'Name', 'Phone')
def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]):
if not tarfields:
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 9ab1ef7799..feeb96898a 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -11,11 +11,12 @@ from frappe.model import default_fields, table_fields
from frappe.model.naming import set_new_name
from frappe.model.utils.link_count import notify_link_count
from frappe.modules import load_doctype_module
-from frappe.model import display_fieldtypes, data_fieldtypes
+from frappe.model import display_fieldtypes
from frappe.utils.password import get_decrypted_password, set_encrypted_password
-from frappe.utils import (cint, flt, now, cstr, strip_html, getdate, get_datetime, to_timedelta,
+from frappe.utils import (cint, flt, now, cstr, strip_html,
sanitize_html, sanitize_email, cast_fieldtype)
from frappe.utils.html_utils import unescape_html
+from bs4 import BeautifulSoup
max_positive_value = {
'smallint': 2 ** 15,
@@ -288,7 +289,7 @@ class BaseDocument(object):
if k in default_fields:
del doc[k]
- for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers"):
+ for key in ("_user_tags", "__islocal", "__onload", "_liked_by", "__run_link_triggers", "__unsaved"):
if self.get(key):
doc[key] = self.get(key)
@@ -564,13 +565,20 @@ class BaseDocument(object):
for data_field in self.meta.get_data_fields():
data = self.get(data_field.fieldname)
data_field_options = data_field.get("options")
+ old_fieldtype = data_field.get("oldfieldtype")
+
+ if old_fieldtype and old_fieldtype != "Data":
+ continue
if data_field_options == "Email":
if (self.owner in STANDARD_USERS) and (data in STANDARD_USERS):
- return
+ continue
for email_address in frappe.utils.split_emails(data):
frappe.utils.validate_email_address(email_address, throw=True)
+ if data_field_options == "Name":
+ frappe.utils.validate_name(data, throw=True)
+
if data_field_options == "Phone":
frappe.utils.validate_phone_number(data, throw=True)
@@ -678,7 +686,7 @@ class BaseDocument(object):
# doesn't look like html so no need
continue
- elif "" in value and not ("