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 ("" in value and not bool(BeautifulSoup(value, "html.parser").find()): # should be handled separately via the markdown converter function continue diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index f697d8051a..91fb079fca 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -74,11 +74,9 @@ def set_user_and_static_default_values(doc): def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records, default_doc): # don't set defaults for "User" link field using User Permissions! if df.fieldtype == "Link" and df.options != "User": - # 1 - look in user permissions only for document_type==Setup - # We don't want to include permissions of transactions to be used for defaults. - if (frappe.get_meta(df.options).document_type=="Setup" - and not df.ignore_user_permissions and default_doc): - return default_doc + # If user permission has Is Default enabled or single-user permission has found against respective doctype. + if (not df.ignore_user_permissions and default_doc): + return default_doc # 2 - Look in user defaults user_default = defaults.get(df.fieldname) diff --git a/frappe/model/document.py b/frappe/model/document.py index f2495e9e20..5e01f5e65f 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -329,6 +329,10 @@ class Document(BaseDocument): self.update_children() self.run_post_save_methods() + # clear unsaved flag + if hasattr(self, "__unsaved"): + delattr(self, "__unsaved") + return self def copy_attachments_from_amended_from(self): diff --git a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py index 4388d3c849..12680609d5 100644 --- a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py +++ b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py @@ -1,6 +1,10 @@ import frappe def execute(): + frappe.reload_doc("contacts", "doctype", "contact_email") + frappe.reload_doc("contacts", "doctype", "contact_phone") + frappe.reload_doc("contacts", "doctype", "contact") + contact_details = frappe.db.sql(""" SELECT `name`, `email_id`, `phone`, `mobile_no`, `modified_by`, `creation`, `modified` @@ -10,10 +14,6 @@ def execute(): and `tabContact Email`.email_id=`tabContact`.email_id) """, as_dict=True) - frappe.reload_doc("contacts", "doctype", "contact_email") - frappe.reload_doc("contacts", "doctype", "contact_phone") - frappe.reload_doc("contacts", "doctype", "contact") - email_values = [] phone_values = [] for count, contact_detail in enumerate(contact_details): diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index d0a3379609..4e049d120a 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -441,18 +441,16 @@ frappe.PrintFormatBuilder = Class.extend({ }); }, setup_field_settings: function() { - - this.page.main.find(".field-settings").on("click", () => { - var field = $(this).parent(); - + this.page.main.find(".field-settings").on("click", e => { + const field = $(e.currentTarget).parent(); // new dialog var d = new frappe.ui.Dialog({ title: "Set Properties", fields: [ { - label:__("Label"), - fieldname:"label", - fieldtype:"Data" + label: __("Label"), + fieldname: "label", + fieldtype: "Data" }, { label: __("Align Value"), @@ -485,7 +483,7 @@ frappe.PrintFormatBuilder = Class.extend({ }); // set current value - if(field.attr('data-align')) { + if (field.attr('data-align')) { d.set_value('align', field.attr('data-align')); } else { d.set_value('align', 'left'); diff --git a/frappe/public/build.json b/frappe/public/build.json index 75a89e5010..7f55924a6b 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -90,6 +90,7 @@ "public/css/font-awesome.css", "public/css/octicons/octicons.css", "public/less/desk.less", + "public/less/module.less", "public/less/flex.less", "public/less/indicator.less", "public/less/avatar.less", diff --git a/frappe/public/js/frappe/form/controls/code.js b/frappe/public/js/frappe/form/controls/code.js index bdf36b706a..6b40201001 100644 --- a/frappe/public/js/frappe/form/controls/code.js +++ b/frappe/public/js/frappe/form/controls/code.js @@ -10,11 +10,11 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ .appendTo(this.input_area); this.expanded = false; - this.$expand_button = $(``).click(() => { + this.$expand_button = $(``).click(() => { this.expanded = !this.expanded; this.refresh_height(); - }).insertAfter(this.ace_editor_target); - + this.toggle_label(); + }).appendTo(this.$input_wrapper); // styling this.ace_editor_target.addClass('border rounded'); this.ace_editor_target.css('height', 300); @@ -37,6 +37,11 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ this.editor.resize(); }, + toggle_label() { + const button_label = this.expanded ? __('Collapse') : __('Expand'); + this.$expand_button.text(button_label); + }, + set_language() { const language_map = { 'Javascript': 'ace/mode/javascript', diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index a7f0050d65..c943ec89bb 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -96,6 +96,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ if(this.df.options == 'Phone') { this.df.invalid = !validate_phone(v); return v; + } else if (this.df.options == 'Name') { + this.df.invalid = !validate_name(v); + return v; } else if(this.df.options == 'Email') { var email_list = frappe.utils.split_emails(v); if (!email_list) { diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 02caf25557..a145e47149 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -69,7 +69,7 @@ frappe.ui.form.Sidebar = Class.extend({ }, refresh: function() { - if(this.frm.doc.__islocal) { + if (this.frm.doc.__islocal) { this.sidebar.toggle(false); } else { this.sidebar.toggle(true); @@ -81,12 +81,34 @@ frappe.ui.form.Sidebar = Class.extend({ } this.frm.viewers.refresh(); this.frm.tags && this.frm.tags.refresh(this.frm.get_docinfo().tags); - this.sidebar.find(".modified-by").html(__("{0} edited this {1}", - ["" + frappe.user.full_name(this.frm.doc.modified_by) + "", - "
" + comment_when(this.frm.doc.modified)])); - this.sidebar.find(".created-by").html(__("{0} created this {1}", - ["" + frappe.user.full_name(this.frm.doc.owner) + "", - "
" + comment_when(this.frm.doc.creation)])); + + if (this.frm.doc.route && cint(frappe.boot.website_tracking_enabled)) { + let route = this.frm.doc.route; + frappe.utils.get_page_view_count(route).then((res) => { + this.sidebar + .find(".pageview-count") + .html( + __("{0} Page Views", [String(res.message).bold()]) + ); + }); + } + + this.sidebar + .find(".modified-by") + .html( + __("{0} edited this {1}", [ + frappe.user.full_name(this.frm.doc.modified_by).bold(), + "
" + comment_when(this.frm.doc.modified), + ]) + ); + this.sidebar + .find(".created-by") + .html( + __("{0} created this {1}", [ + frappe.user.full_name(this.frm.doc.owner).bold(), + "
" + comment_when(this.frm.doc.creation), + ]) + ); this.refresh_like(); frappe.ui.form.set_user_image(this.frm); diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index b611557c43..30b2205bae 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -105,6 +105,7 @@ diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 1a118be5db..7be7fc5baa 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -137,10 +137,8 @@ $.extend(frappe.model, { // don't set defaults for "User" link field using User Permissions! if (df.fieldtype==="Link" && df.options!=="User") { - // 1 - look in user permissions for document_type=="Setup". - // We don't want to include permissions of transactions to be used for defaults. - if (df.linked_document_type==="Setup" - && has_user_permissions && default_doc) { + // If user permission has Is Default enabled or single-user permission has found against respective doctype. + if (has_user_permissions && default_doc) { return default_doc; } @@ -161,10 +159,6 @@ $.extend(frappe.model, { user_default = frappe.boot.user.last_selected_values[df.options]; } - if (!user_default && default_doc) { - user_default = default_doc; - } - var is_allowed_user_default = user_default && (!has_user_permissions || allowed_records.includes(user_default)); diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index e919012664..6ad15e44bf 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -352,3 +352,9 @@ frappe.utils.new_auto_repeat_prompt = function(frm) { __('Save') ); } + +frappe.utils.get_page_view_count = function(route) { + return frappe.call("frappe.website.doctype.web_page_view.web_page_view.get_page_view_count", { + path: route + }); +}; diff --git a/frappe/public/js/frappe/utils/datatype.js b/frappe/public/js/frappe/utils/datatype.js index 16f87b872f..1b9206f434 100644 --- a/frappe/public/js/frappe/utils/datatype.js +++ b/frappe/public/js/frappe/utils/datatype.js @@ -48,6 +48,10 @@ window.validate_phone = function(txt) { return frappe.utils.validate_type(txt, "phone"); }; +window.validate_name = function(txt) { + return frappe.utils.validate_type(txt, "name"); +}; + window.nth = function(number) { number = cint(number); var s = 'th'; @@ -73,4 +77,4 @@ window.has_common = function(list1, list2) { if(in_list(list2, list1[i]))return true; } return false; -}; \ No newline at end of file +}; diff --git a/frappe/public/js/frappe/utils/pretty_date.js b/frappe/public/js/frappe/utils/pretty_date.js index 060ae73a98..ef235ed3b1 100644 --- a/frappe/public/js/frappe/utils/pretty_date.js +++ b/frappe/public/js/frappe/utils/pretty_date.js @@ -13,7 +13,7 @@ function prettyDate(date, mini) { // Return short format of time difference if (day_diff == 0) { if (diff < 60) { - return __("Now"); + return __("now"); } else if (diff < 3600) { return __("{0} m", [Math.floor(diff / 60)]); } else if (diff < 86400) { @@ -21,20 +21,20 @@ function prettyDate(date, mini) { } } else { if (day_diff < 7) { - return __("{0} D", [day_diff]); + return __("{0} d", [day_diff]); } else if (day_diff < 31) { - return __("{0} W", [Math.ceil(day_diff / 7)]); + return __("{0} w", [Math.ceil(day_diff / 7)]); } else if (day_diff < 365) { return __("{0} M", [Math.ceil(day_diff / 30)]); } else { - return __("{0} Y", [Math.ceil(day_diff / 365)]); + return __("{0} y", [Math.ceil(day_diff / 365)]); } } } else { // Return long format of time difference if (day_diff == 0) { if (diff < 60) { - return __("Just now"); + return __("just now"); } else if (diff < 120) { return __("1 minute ago"); } else if (diff < 3600) { @@ -46,7 +46,7 @@ function prettyDate(date, mini) { } } else { if (day_diff == 1) { - return __("Yesterday"); + return __("yesterday"); } else if (day_diff < 7) { return __("{0} days ago", [day_diff]); } else if (day_diff < 14) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 0f27e97178..1afdbfd81c 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -237,6 +237,9 @@ Object.assign(frappe.utils, { case "phone": regExp = /^([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$/; break; + case "name": + regExp = /^[\w][\w'-]*([ \w][\w'-]+)*$/; + break; case "number": regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/; break; @@ -745,7 +748,36 @@ Object.assign(frappe.utils, { }); return $el; - } + }, + + get_browser() { + var ua = navigator.userAgent, + tem, + M = + ua.match( + /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i + ) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+)/g.exec(ua) || []; + return { name: "IE", version: tem[1] || "" }; + } + if (M[1] === "Chrome") { + tem = ua.match(/\bOPR|Edge\/(\d+)/); + if (tem != null) { + return { name: "Opera", version: tem[1] }; + } + } + M = M[2] + ? [M[1], M[2]] + : [navigator.appName, navigator.appVersion, "-?"]; + if ((tem = ua.match(/version\/(\d+)/i)) != null) { + M.splice(1, 1, tem[1]); + } + return { + name: M[0], + version: M[1], + }; + }, }); // Array de duplicate diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 2aef1a8218..1c1049391f 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -6,9 +6,10 @@ frappe.breadcrumbs = { preferred: { "File": "", + "Video": "", "Dashboard": "Customization", "Dashboard Chart": "Customization", - "Dashboard Chart Source": "Customization", + "Dashboard Chart Source": "Customization" }, module_map: { diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 17b32b3a52..ba290417f5 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -20,7 +20,8 @@ frappe.views.CommunicationComposer = Class.extend({ primary_action: function() { me.delete_saved_draft(); me.send_action(); - } + }, + minimizable: true }); ['recipients', 'cc', 'bcc'].forEach(field => { diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less index b72d249aab..8e43b05122 100644 --- a/frappe/public/less/form.less +++ b/frappe/public/less/form.less @@ -770,6 +770,7 @@ h6.uppercase, .h6.uppercase { .help-box { margin-top: 3px; + margin-bottom: 6px; } pre { diff --git a/frappe/public/less/module.less b/frappe/public/less/module.less new file mode 100644 index 0000000000..f924778864 --- /dev/null +++ b/frappe/public/less/module.less @@ -0,0 +1,147 @@ +@import "variables.less"; + +.module-head { + padding: 15px 30px; + border-bottom: 1px solid @light-border-color; +} + +.module-head h1 { + padding: 0px; + margin: 0px; +} + +.module-body { + padding: 0px 15px; + + .section-head { + margin-bottom: 15px; + margin-top: 0px; + } +} + +.module-section { + border-bottom: 1px solid @light-border-color; + + .module-section-link { + line-height: 1.5em; + // font-size: 14px; + } +} + +.module-section-column { + padding: 30px; +} + +@media(min-width: @screen-xs) { + .module-section:nth-child(even) { + background-color: @light-bg; + } + + .module-section:last-child { + border-bottom: none; + } +} + +@media(max-width: @screen-sm) { + .module-body { + margin-top: 15px; + border-top: 1px solid @border-color; + } +} + +@media(max-width: @screen-xs) { + .module-body { + margin-top: 0; + border-top: 1px solid transparent; + } +} + +@media(max-width: @screen-xs) { + .module-section { + border: none; + } + + .module-section-column { + border-bottom: 1px solid @light-border-color; + } + + .module-section-column:nth-child(even) { + background-color: @light-bg; + } + + .module-section:last-child .module-section-column:last-child { + border-bottom: none; + } +} + + +.module-item { + margin: 0px; + padding: 7px; + font-weight: 400; + border-bottom: 1px solid @border-color; + cursor: pointer; + transition: 0.2s; + -webkit-transition: 0.2s; +} + +.module-item h4 { + display: inline-block; +} + +.module-item .module-item-description { + margin-top: -5px; +} + +.module-item .badge { + margin-top: -2px; + margin-left: 3px; +} + +.module-item:hover, .module-item:focus { + background-color: @panel-bg; +} + +.module-item:last-child { + border: none; +} + +.module-link.active .icon-chevron-right { + margin-top: 4px; + display: block !important; +} + +.module-item-progress { + margin-bottom: 10px; + height: 17px; +} + +.module-item-progress-total { + height: 7px; + background-color: #999999; + width: 0px; +} + +.module-item-progress-open { + height: 7px; + background-color: red; + width: 0px; +} + +@media(max-width: @screen-xs) { + + body[data-route^="Module"] { + .page-title { + width: 100%; + } + + + .page-actions { + display: none !important; + } + + .layout-main-section { + border-bottom: 0px; + } + } +} diff --git a/frappe/public/less/sidebar.less b/frappe/public/less/sidebar.less index ac5a5c33d5..28dae1a948 100644 --- a/frappe/public/less/sidebar.less +++ b/frappe/public/less/sidebar.less @@ -273,7 +273,8 @@ body[data-route^="Module"] .main-menu { } .layout-side-section .form-sidebar { - .modified-by { + .modified-by, + .pageview-count { margin-bottom: 15px; } } diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 649d3bf72c..34432839bb 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -81,13 +81,29 @@ def validate_phone_number(phone_number, throw=False): return False phone_number = phone_number.strip() - match = re.match("([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number) + match = re.match(r"([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number) if not match and throw: frappe.throw(frappe._("{0} is not a valid Phone Number").format(phone_number), frappe.InvalidPhoneNumberError) return bool(match) +def validate_name(name, throw=False): + """Returns True if the name is valid + valid names may have unicode and ascii characters, dash, quotes, numbers + anything else is considered invalid + """ + if not name: + return False + + name = name.strip() + match = re.match(r"^[\w][\w\'\-]*([ \w][\w\'\-]+)*$", name) + + if not match and throw: + frappe.throw(frappe._("{0} is not a valid Name").format(name), frappe.InvalidNameError) + + return bool(match) + def validate_email_address(email_str, throw=False): """Validates the email string""" email = email_str = (email_str or "").strip() diff --git a/frappe/utils/change_log.py b/frappe/utils/change_log.py index c94a247796..776fb825c2 100644 --- a/frappe/utils/change_log.py +++ b/frappe/utils/change_log.py @@ -174,9 +174,12 @@ def parse_latest_non_beta_release(response): Returns json : json object pertaining to the latest non-beta release """ - for release in response: - if release['prerelease'] == True: continue - return release + version_list = [release.get('tag_name').strip('v') for release in response if not release.get('prerelease')] + + if version_list: + return sorted(version_list, key=Version, reverse=True)[0] + + return None def check_release_on_github(app): # Check if repo remote is on github @@ -199,12 +202,11 @@ def check_release_on_github(app): org_name = remote_url.split('/')[3] r = requests.get('https://api.github.com/repos/{}/{}/releases'.format(org_name, app)) - if r.status_code == 200 and r.json(): + if r.ok: lastest_non_beta_release = parse_latest_non_beta_release(r.json()) - return Version(lastest_non_beta_release['tag_name'].strip('v')), org_name - else: - # In case of an improper response or if there are no releases - return None + return Version(lastest_non_beta_release), org_name + # In case of an improper response or if there are no releases + return None def add_message_to_redis(update_json): # "update-message" will store the update message string diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index d40e2565cb..5a77434cde 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -5,7 +5,7 @@ from logging.handlers import RotatingFileHandler from six import text_type default_log_level = logging.DEBUG -LOG_FILENAME = '../logs/frappe.log' +LOG_FILENAME = '../logs/{}-frappe.log'.format(frappe.local.site) def get_logger(module, with_more_info=True): if module in frappe.loggers: @@ -57,4 +57,3 @@ def set_log_level(level): '''Use this method to set log level to something other than the default DEBUG''' frappe.log_level = getattr(logging, (level or '').upper(), None) or default_log_level frappe.loggers = {} - diff --git a/frappe/website/doctype/web_page_view/__init__.py b/frappe/website/doctype/web_page_view/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/web_page_view/test_web_page_view.py b/frappe/website/doctype/web_page_view/test_web_page_view.py new file mode 100644 index 0000000000..d51727ec68 --- /dev/null +++ b/frappe/website/doctype/web_page_view/test_web_page_view.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 TestWebPageView(unittest.TestCase): + pass diff --git a/frappe/website/doctype/web_page_view/web_page_view.js b/frappe/website/doctype/web_page_view/web_page_view.js new file mode 100644 index 0000000000..77a047e408 --- /dev/null +++ b/frappe/website/doctype/web_page_view/web_page_view.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Web Page View', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/website/doctype/web_page_view/web_page_view.json b/frappe/website/doctype/web_page_view/web_page_view.json new file mode 100644 index 0000000000..7a1a210d62 --- /dev/null +++ b/frappe/website/doctype/web_page_view/web_page_view.json @@ -0,0 +1,75 @@ +{ + "actions": [], + "creation": "2020-04-15 22:54:46.009703", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "path", + "referrer", + "browser", + "browser_version", + "date" + ], + "fields": [ + { + "fieldname": "path", + "fieldtype": "Data", + "label": "Path", + "set_only_once": 1 + }, + { + "fieldname": "referrer", + "fieldtype": "Data", + "label": "Referrer", + "search_index": 1, + "set_only_once": 1 + }, + { + "fieldname": "browser", + "fieldtype": "Data", + "label": "Browser", + "search_index": 1, + "set_only_once": 1 + }, + { + "fieldname": "browser_version", + "fieldtype": "Data", + "label": "Browser Version", + "set_only_once": 1 + }, + { + "fieldname": "date", + "fieldtype": "Datetime", + "label": "Date", + "set_only_once": 1 + } + ], + "in_create": 1, + "links": [], + "modified": "2020-04-15 23:31:27.517793", + "modified_by": "Administrator", + "module": "Website", + "name": "Web Page View", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "path", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/website/doctype/web_page_view/web_page_view.py b/frappe/website/doctype/web_page_view/web_page_view.py new file mode 100644 index 0000000000..08625f9d6f --- /dev/null +++ b/frappe/website/doctype/web_page_view/web_page_view.py @@ -0,0 +1,43 @@ +# -*- 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 WebPageView(Document): + pass + + +@frappe.whitelist(allow_guest=True) +def make_view_log(path, referrer=None, browser=None, version=None, url=None, user_tz=None): + request_dict = frappe.request.__dict__ + user_agent = request_dict.get('environ', {}).get('HTTP_USER_AGENT') + + is_unique = True + if referrer.startswith(url): + is_unique = False + + if path.startswith('/'): + path = path[1:] + + if is_tracking_enabled(): + view = frappe.new_doc("Web Page View") + view.path = path + view.referrer = referrer + view.browser = browser + view.browser_version = version + view.time_zone = user_tz + view.user_agent = user_agent + view.is_unique = is_unique + view.insert(ignore_permissions=True) + + return + +@frappe.whitelist() +def get_page_view_count(path): + return frappe.db.count("Web Page View", filters={'path': path}) + +def is_tracking_enabled(): + return frappe.db.get_value("Website Settings", "Website Settings", "enable_view_tracking") \ No newline at end of file diff --git a/frappe/website/doctype/web_view/web_view.json b/frappe/website/doctype/web_view/web_view.json index 8c42f51421..d4ccbad0e4 100644 --- a/frappe/website/doctype/web_view/web_view.json +++ b/frappe/website/doctype/web_view/web_view.json @@ -3,7 +3,6 @@ "allow_guest_to_view": 1, "allow_import": 1, "allow_rename": 1, - "autoname": "field:title", "beta": 1, "creation": "2020-03-16 15:28:03.828741", "doctype": "DocType", @@ -117,7 +116,7 @@ "has_web_view": 1, "is_published_field": "published", "links": [], - "modified": "2020-04-19 12:25:48.014935", + "modified": "2020-04-22 00:54:23.413077", "modified_by": "Administrator", "module": "Website", "name": "Web View", diff --git a/frappe/website/doctype/website_settings/website_settings.js b/frappe/website/doctype/website_settings/website_settings.js index 38e1ff993a..be294258f4 100644 --- a/frappe/website/doctype/website_settings/website_settings.js +++ b/frappe/website/doctype/website_settings/website_settings.js @@ -56,6 +56,10 @@ frappe.ui.form.on('Website Settings', { }); }, + enable_view_tracking: function(frm) { + frappe.boot.website_tracking_enabled = frm.doc.enable_view_tracking; + }, + set_parent_options: function(frm, doctype, name) { var item = frappe.get_doc(doctype, name); if(item.parentfield === "top_bar_items") { diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index f9ed247c0d..708d2a0473 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -33,6 +33,7 @@ "footer_items", "hide_footer_signup", "integrations", + "enable_view_tracking", "enable_google_indexing", "authorize_api_indexing_access", "indexing_refresh_token", @@ -196,7 +197,7 @@ "collapsible": 1, "fieldname": "integrations", "fieldtype": "Section Break", - "label": "Google Integrations" + "label": "Integrations" }, { "description": "Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.", @@ -330,6 +331,12 @@ "fieldtype": "Button", "label": "Authorize API Indexing Access" }, + { + "default": "0", + "fieldname": "enable_view_tracking", + "fieldtype": "Check", + "label": "Enable In App Website Tracking" + }, { "default": "Standard", "fieldname": "footer_type", @@ -364,7 +371,7 @@ "issingle": 1, "links": [], "max_attachments": 10, - "modified": "2020-04-21 16:46:59.947403", + "modified": "2020-04-21 12:37:44.070662", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 49b93fae1d..ead48425ed 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -118,7 +118,7 @@ def get_website_settings(): for k in ["banner_html", "brand_html", "copyright", "twitter_share_via", "facebook_share", "google_plus_one", "twitter_share", "linked_in_share", "disable_signup", "hide_footer_signup", "head_html", "title_prefix", - "navbar_search"]: + "navbar_search", "enable_view_tracking"]: if hasattr(settings, k): context[k] = settings.get(k) diff --git a/frappe/www/update-password.html b/frappe/www/update-password.html index f0ee0688d4..d12be86d12 100644 --- a/frappe/www/update-password.html +++ b/frappe/www/update-password.html @@ -9,7 +9,7 @@ {{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}
-
+ @@ -32,8 +32,8 @@