diff --git a/README.md b/README.md index 50d715ad72..2a8fba0af4 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@
- - + + diff --git a/frappe/__init__.py b/frappe/__init__.py index e2d8041992..e19327fcff 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if PY2: reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.1.0' +__version__ = '12.0.0-dev' __title__ = "Frappe Framework" local = Local() diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.py b/frappe/automation/doctype/assignment_rule/assignment_rule.py index 5d8dab90ce..0a5d85636f 100644 --- a/frappe/automation/doctype/assignment_rule/assignment_rule.py +++ b/frappe/automation/doctype/assignment_rule/assignment_rule.py @@ -165,7 +165,7 @@ def reopen_closed_assignment(doc): return True def apply(doc, method=None, doctype=None, name=None): - if frappe.flags.in_patch or frappe.flags.in_install: + if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard: return if not doc and doctype and name: diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py index baa1bcc075..154cb599e1 100644 --- a/frappe/automation/doctype/milestone_tracker/milestone_tracker.py +++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.py @@ -30,6 +30,10 @@ class MilestoneTracker(Document): )).insert(ignore_permissions=True) def evaluate_milestone(doc, event): + if (frappe.flags.in_install + or frappe.flags.in_migrate + or frappe.flags.in_setup_wizard): + return for d in frappe.cache_manager.get_doctype_map('Milestone Tracker', doc.doctype, dict(document_type = doc.doctype, disabled=0)): frappe.get_doc('Milestone Tracker', d.name).apply(doc) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 1faa02ec63..9959ba97bb 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, json import frappe.defaults +from frappe.model.document import Document from frappe.desk.notifications import (delete_notification_count_for, clear_notifications) @@ -22,6 +23,10 @@ user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified", "linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map') +count_cache_blacklist = ["Version", "Tag", "ToDo", "List Filter", "Note Seen By", "Notification Log", + "Document Follow", "Communication", "Email Queue", "Deleted Document", "File", "Email Queue Recipient" + "Comment", "Has Role", "Attendance", "Route History"] + def clear_user_cache(user=None): cache = frappe.cache() @@ -116,9 +121,23 @@ def clear_doctype_map(doctype, name): cache_key = frappe.scrub(doctype) + '_map' frappe.cache().hdel(cache_key, name) -def build_table_count_cache(*args, **kwargs): - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_import: +def build_table_count_cache(doc=None, method=None, *args, **kwargs): + if (frappe.flags.in_patch + or frappe.flags.in_install + or frappe.flags.in_migrate + or frappe.flags.in_import + or frappe.flags.in_setup_wizard): return + + if doc and isinstance(doc, Document): + doctype = doc.doctype + + if doc.meta.istable: + return + + if doctype in count_cache_blacklist: + return + _cache = frappe.cache() data = frappe.db.multisql({ "mariadb": """ @@ -138,7 +157,11 @@ def build_table_count_cache(*args, **kwargs): return counts def build_domain_restriced_doctype_cache(*args, **kwargs): - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_import: + if (frappe.flags.in_patch + or frappe.flags.in_install + or frappe.flags.in_migrate + or frappe.flags.in_import + or frappe.flags.in_setup_wizard): return _cache = frappe.cache() active_domains = frappe.get_active_domains() @@ -149,7 +172,11 @@ def build_domain_restriced_doctype_cache(*args, **kwargs): return doctypes def build_domain_restriced_page_cache(*args, **kwargs): - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_import: + if (frappe.flags.in_patch + or frappe.flags.in_install + or frappe.flags.in_migrate + or frappe.flags.in_import + or frappe.flags.in_setup_wizard): return _cache = frappe.cache() active_domains = frappe.get_active_domains() diff --git a/frappe/core/doctype/data_import/importer_new.py b/frappe/core/doctype/data_import/importer_new.py index e12ad9925f..1f446cfb39 100644 --- a/frappe/core/doctype/data_import/importer_new.py +++ b/frappe/core/doctype/data_import/importer_new.py @@ -9,7 +9,7 @@ import timeit import frappe from datetime import datetime from frappe import _ -from frappe.utils import cint, flt, update_progress_bar, cstr +from frappe.utils import cint, flt, update_progress_bar, cstr, DATETIME_FORMAT from frappe.utils.csvutils import read_csv_content from frappe.utils.xlsxutils import ( read_xlsx_file_from_attached_file, @@ -345,6 +345,9 @@ class Importer: return columns_with_serial_no, data_with_serial_no def parse_value(self, value, df): + if isinstance(value, datetime) and df.fieldtype in ["Date", "Datetime"]: + return value + value = cstr(value) # convert boolean values to 0 or 1 @@ -362,14 +365,13 @@ class Importer: return value def parse_date_format(self, value, df): - date_format = self.get_date_format_for_df(df) - if date_format: - try: - return datetime.strptime(value, date_format) - except: - # ignore date values that dont match the format - # import will break for these values later - pass + date_format = self.get_date_format_for_df(df) or DATETIME_FORMAT + try: + return datetime.strptime(value, date_format) + except ValueError: + # ignore date values that dont match the format + # import will break for these values later + pass return value def get_date_format_for_df(self, df): @@ -396,7 +398,8 @@ class Importer: date_values = [ row[column_index] for row in self.data[:PARSE_ROW_COUNT] if row[column_index] ] - date_formats = [guess_date_format(d) for d in date_values] + date_formats = [guess_date_format(d) if isinstance(d, str) else None + for d in date_values] if not date_formats: return max_occurred_date_format = max(set(date_formats), key=date_formats.count) @@ -821,7 +824,11 @@ class Importer: id_fieldname = self.get_id_fieldname(self.doctype) id_value = doc[id_fieldname] existing_doc = frappe.get_doc(self.doctype, id_value) - existing_doc.flags.via_data_import = self.data_import.name + existing_doc.flags.updater_reference = { + 'doctype': self.data_import.doctype, + 'docname': self.data_import.name, + 'label': _('via Data Import') + } existing_doc.update(doc) existing_doc.save() return existing_doc diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index e3242887c7..9b04ebb7ad 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-02-22 01:27:33", "doctype": "DocType", @@ -116,7 +117,7 @@ "fieldname": "precision", "fieldtype": "Select", "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9", + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9", "print_hide": 1 }, { @@ -450,8 +451,9 @@ ], "idx": 1, "istable": 1, - "modified": "2019-11-15 12:28:24.461628", - "modified_by": "umair@erpnext.com", + "links": [], + "modified": "2020-03-16 14:49:49.672099", + "modified_by": "Administrator", "module": "Core", "name": "DocField", "owner": "Administrator", diff --git a/frappe/core/doctype/doctype/boilerplate/templates/controller_row.html b/frappe/core/doctype/doctype/boilerplate/templates/controller_row.html index 3cc8ce2c2d..66fe744830 100644 --- a/frappe/core/doctype/doctype/boilerplate/templates/controller_row.html +++ b/frappe/core/doctype/doctype/boilerplate/templates/controller_row.html @@ -1,4 +1,4 @@
- {{{{ title }}}} + {{{{ doc.title or doc.name }}}}
- \ No newline at end of file + diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 3d434edc40..08e082c234 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -24,6 +24,7 @@ from frappe.modules import make_boilerplate, get_doc_path from frappe.database.schema import validate_column_name, validate_column_length from frappe.model.docfield import supports_translation from frappe.modules.import_file import get_file_path +from frappe.model.meta import Meta class InvalidFieldNameError(frappe.ValidationError): pass @@ -277,7 +278,7 @@ class DocType(Document): """Update database schema, make controller templates if `custom` is not set and clear cache.""" self.delete_duplicate_custom_fields() try: - frappe.db.updatedb(self.name, self) + frappe.db.updatedb(self.name, Meta(self)) except Exception as e: print("\n\nThere was an issue while migrating the DocType: {}\n".format(self.name)) raise e diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 1fa05bd1c3..f2c62ad1a3 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -37,6 +37,7 @@ "allow_login_using_user_name", "allow_error_traceback", "password_settings", + "logout_on_password_reset", "force_user_to_reset_password", "column_break_31", "enable_password_policy", @@ -149,7 +150,7 @@ "fieldname": "currency_precision", "fieldtype": "Select", "label": "Currency Precision", - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9" + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { "collapsible": 1, @@ -407,12 +408,18 @@ "fieldname": "dormant_days", "fieldtype": "Int", "label": "Run Jobs only Daily if Inactive For (Days)" + }, + { + "default": "1", + "fieldname": "logout_on_password_reset", + "fieldtype": "Check", + "label": "Logout All Sessions on Password Reset" } ], "icon": "fa fa-cog", "issingle": 1, "links": [], - "modified": "2020-01-31 06:03:35.595384", + "modified": "2020-03-16 14:50:40.914532", "modified_by": "Administrator", "module": "Core", "name": "System Settings", diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index 0d981c9e9e..d4c0fa98ed 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -224,3 +224,4 @@ class TestUser(unittest.TestCase): def delete_contact(user): frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user) + frappe.db.sql("DELETE FROM `tabContact Email` WHERE `email_id`= %s", user) diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index b71c9555e1..5ebde7e7bd 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "creation": "2014-03-11 14:55:00", @@ -178,7 +179,7 @@ { "fieldname": "time_zone", "fieldtype": "Select", - "label": "Timezone" + "label": "Time Zone" }, { "description": "Get your globally recognized avatar from Gravatar.com", @@ -302,7 +303,7 @@ "default": "0", "fieldname": "logout_all_sessions", "fieldtype": "Check", - "label": "Logout from all devices while changing Password" + "label": "Logout From All Devices After Changing Password" }, { "fieldname": "reset_password_key", @@ -338,7 +339,7 @@ "default": "0", "fieldname": "document_follow_notify", "fieldtype": "Check", - "label": "Send Notifications for documents followed by me" + "label": "Send Notifications For Documents Followed By Me" }, { "default": "Daily", @@ -359,7 +360,7 @@ "default": "1", "fieldname": "thread_notify", "fieldtype": "Check", - "label": "Send Notifications for Email threads" + "label": "Send Notifications For Email Threads" }, { "default": "0", @@ -496,7 +497,7 @@ "description": "If enabled, user can login from any IP Address using Two Factor Auth, this can also be set for all users in System Settings", "fieldname": "bypass_restrict_ip_check_if_2fa_enabled", "fieldtype": "Check", - "label": "Bypass restricted IP Address check If Two Factor Auth Enabled" + "label": "Bypass Restricted IP Address Check If Two Factor Auth Enabled" }, { "fieldname": "column_break1", @@ -585,8 +586,9 @@ "icon": "fa fa-user", "idx": 413, "image_field": "user_image", + "links": [], "max_attachments": 5, - "modified": "2019-10-22 14:16:34.810223", + "modified": "2020-03-23 22:59:26.154985", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index b411f809cd..ddad3a91fb 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -101,7 +101,8 @@ class User(Document): frappe.enqueue( 'frappe.core.doctype.user.user.create_contact', user=self, - ignore_mandatory=True + ignore_mandatory=True, + now=frappe.flags.in_test ) if self.name not in ('Administrator', 'Guest') and not self.user_image: frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name) @@ -554,7 +555,8 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password= else: user = res['user'] - _update_password(user, new_password, logout_all_sessions=int(logout_all_sessions)) + logout_all_sessions = cint(logout_all_sessions) or frappe.db.get_single_value("System Settings", "logout_on_password_reset") + _update_password(user, new_password, logout_all_sessions=cint(logout_all_sessions)) user_doc, redirect_url = reset_user_data(user) @@ -1038,8 +1040,8 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False): from frappe.contacts.doctype.contact.contact import get_contact_name if user.name in ["Administrator", "Guest"]: return - contact_exists = get_contact_name(user.email) - if not contact_exists: + contact_name = get_contact_name(user.email) + if not contact_name: contact = frappe.get_doc({ "doctype": "Contact", "first_name": user.first_name, @@ -1058,7 +1060,7 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False): contact.add_phone(user.mobile_no, is_primary_mobile_no=True) contact.insert(ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory) else: - contact = frappe.get_doc("Contact", contact_exists) + contact = frappe.get_doc("Contact", contact_name) contact.first_name = user.first_name contact.last_name = user.last_name contact.gender = user.gender diff --git a/frappe/core/doctype/version/version.py b/frappe/core/doctype/version/version.py index d7a3b62a2c..216cdb1716 100644 --- a/frappe/core/doctype/version/version.py +++ b/frappe/core/doctype/version/version.py @@ -47,7 +47,11 @@ def get_diff(old, new, for_child=False): # capture data import if set data_import = new.flags.via_data_import - out = frappe._dict(changed = [], added = [], removed = [], row_changed = [], data_import=data_import) + updater_reference = new.flags.updater_reference + + out = frappe._dict(changed = [], added = [], removed = [], + row_changed = [], data_import=data_import, updater_reference=updater_reference) + for df in new.meta.fields: if df.fieldtype in no_value_fields and df.fieldtype not in table_fields: continue diff --git a/frappe/core/page/dashboard/dashboard.css b/frappe/core/page/dashboard/dashboard.css index cf1a581c6d..e69de29bb2 100644 --- a/frappe/core/page/dashboard/dashboard.css +++ b/frappe/core/page/dashboard/dashboard.css @@ -1,62 +0,0 @@ -.chart-wrapper { - border: 1px solid #d1d8dd; - border-radius: 4px; - margin: 15px 0; - padding-left: 15px; - padding-right: 15px; - height: 320px; -} - -.chart-container { - top: 50%; - transform: translateY(-50%); -} - -.frappe-chart > text.title { - margin: 0px; - font-size: 14px !important; - font-weight: bold; -} - -.chart-loading-state, .chart-empty-state { - height: 100%; - margin-top: 160px; - text-align: center; -} - -.chart-actions { - position: relative; - right: 0px; - top: 20px; - margin-right: 5px; -} - -.filter-chart { - position: relative; - right: 5px; - top: 20px; -} - -.dashboard-date-field { - width: 14%; - height: 0; - margin-right: 10px; - position: relative; - top: 0px; -} - -.chart-column-container { - position: relative; -} - -.last-synced-text { - position: absolute; - top: 28px; - left: 50px; - font-size: 12px; -} - -.dashboard-graph { - padding-top: 15px; - overflow: hidden; -} \ No newline at end of file diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index ac9221265b..511aac7010 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -22,7 +22,7 @@ class Dashboard { constructor(wrapper) { this.wrapper = $(wrapper); $(`
-
+
`).appendTo(this.wrapper.find(".page-content").empty()); this.container = this.wrapper.find(".dashboard-graph"); this.page = wrapper.page; @@ -78,16 +78,22 @@ class Dashboard { refresh() { this.get_dashboard_doc().then((doc) => { this.dashboard_doc = doc; - this.charts = this.dashboard_doc.charts; + this.charts = this.dashboard_doc.charts + .map(chart => { + return { + chart_name: chart.chart, + label: chart.chart, + ...chart + } + }); - this.charts.map((chart) => { - let chart_container = $("
"); - chart_container.appendTo(this.container); - - frappe.model.with_doc("Dashboard Chart", chart.chart).then( chart_doc => { - let dashboard_chart = new frappe.ui.DashboardChart(chart_doc, chart_container); - dashboard_chart.show(); - }); + this.chart_group = new frappe.widget.WidgetGroup({ + title: null, + container: this.container, + type: "chart", + columns: 2, + allow_sorting: false, + widgets: this.charts, }); }); } diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index b07d38caf0..b274033f80 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -124,7 +124,7 @@ "fieldname": "precision", "fieldtype": "Select", "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9" + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { "fieldname": "options", @@ -376,7 +376,7 @@ "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-02-01 11:50:09.222967", + "modified": "2020-03-16 14:52:43.954709", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 444d9dc495..b1743a96a5 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -85,6 +85,10 @@ frappe.ui.form.on("Customize Form", { if(frm.doc.doc_type) { frappe.customize_form.set_primary_action(frm); + frm.add_custom_button(__('Go to {0} List', [frm.doc.doc_type]), function() { + frappe.set_route('List', frm.doc.doc_type); + }); + frm.add_custom_button(__('Refresh Form'), function() { frm.script_manager.trigger("doc_type"); }, "fa fa-refresh", "btn-default"); diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 5b54491d88..68848d26f6 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -169,12 +169,11 @@ class CustomizeForm(Document): self.flags.update_db = False self.flags.rebuild_doctype_for_global_search = False - - check_email_append_to(self) self.set_property_setters() self.update_custom_fields() self.set_name_translation() validate_fields_for_doctype(self.doc_type) + check_email_append_to(self) if self.flags.update_db: frappe.db.updatedb(self.doc_type) @@ -366,13 +365,49 @@ class CustomizeForm(Document): def validate_fieldtype_change(self, df, old_value, new_value): allowed = False + self.check_length_for_fieldtypes = [] for allowed_changes in allowed_fieldtype_change: if (old_value in allowed_changes and new_value in allowed_changes): allowed = True + if frappe.db.type_map.get(old_value)[1] > frappe.db.type_map.get(new_value)[1]: + self.check_length_for_fieldtypes.append({'df': df, 'old_value': old_value}) + self.validate_fieldtype_length() + else: + self.flags.update_db = True break if not allowed: frappe.throw(_("Fieldtype cannot be changed from {0} to {1} in row {2}").format(old_value, new_value, df.idx)) + def validate_fieldtype_length(self): + for field in self.check_length_for_fieldtypes: + df = field.get('df') + max_length = frappe.db.type_map.get(df.fieldtype)[1] + fieldname = df.fieldname + docs = frappe.db.sql(''' + SELECT name, {fieldname}, LENGTH({fieldname}) AS len + FROM `tab{doctype}` + WHERE LENGTH({fieldname}) > {max_length} + '''.format( + fieldname=fieldname, + doctype=self.doc_type, + max_length=max_length + ), as_dict=True) + links = [] + label = df.label + for doc in docs: + links.append(frappe.utils.get_link_to_form(self.doc_type, doc.name)) + links_str = ', '.join(links) + + if docs: + frappe.throw(_('Value for field {0} is too long in {1}. Length should be lesser than {2} characters') + .format( + frappe.bold(label), + links_str, + frappe.bold(max_length) + ), title=_('Data Too Long'), is_minimizable=len(docs) > 1) + + self.flags.update_db = True + def reset_to_defaults(self): if not self.doc_type: return diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index ab582851b5..57b4cec23b 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -149,7 +149,7 @@ "fieldname": "precision", "fieldtype": "Select", "label": "Precision", - "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9" + "options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" }, { "depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)", @@ -385,7 +385,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2019-12-27 12:50:51.419763", + "modified": "2020-03-16 14:53:40.619043", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/database/database.py b/frappe/database/database.py index 1c08dd714e..b083ff1014 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -48,7 +48,7 @@ class Database(object): def __init__(self, host=None, user=None, password=None, ac_name=None, use_default=0, port=None): self.setup_type_map() - self.host = host or frappe.conf.db_host or 'localhost' + self.host = host or frappe.conf.db_host or '127.0.0.1' self.port = port or frappe.conf.db_port or '' self.user = user or frappe.conf.db_name self.db_name = frappe.conf.db_name diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 243d0f934e..e30ef3293f 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -92,7 +92,7 @@ class PostgresDatabase(Database): # pylint: disable=W0221 def sql(self, *args, **kwargs): - if len(args): + if args: # since tuple is immutable args = list(args) args[0] = modify_query(args[0]) @@ -276,13 +276,13 @@ class PostgresDatabase(Database): # pylint: disable=W1401 return self.sql(''' SELECT a.column_name AS name, - CASE a.data_type + CASE LOWER(a.data_type) WHEN 'character varying' THEN CONCAT('varchar(', a.character_maximum_length ,')') - WHEN 'timestamp without TIME zone' THEN 'timestamp' + WHEN 'timestamp without time zone' THEN 'timestamp' ELSE a.data_type END AS type, COUNT(b.indexdef) AS Index, - COALESCE(a.column_default, NULL) AS default, + SPLIT_PART(COALESCE(a.column_default, NULL), '::', 1) AS default, BOOL_OR(b.unique) AS unique FROM information_schema.columns a LEFT JOIN diff --git a/frappe/database/schema.py b/frappe/database/schema.py index 88cda9340b..28e055f382 100644 --- a/frappe/database/schema.py +++ b/frappe/database/schema.py @@ -13,7 +13,7 @@ class DBTable: def __init__(self, doctype, meta=None): self.doctype = doctype self.table_name = 'tab{}'.format(doctype) - self.meta = meta or frappe.get_meta(doctype) + self.meta = meta or frappe.get_meta(doctype, False) self.columns = {} self.current_columns = {} @@ -65,64 +65,35 @@ class DBTable: """ get columns from docfields and custom fields """ - fl = frappe.db.sql("SELECT * FROM `tabDocField` WHERE parent = %s", self.doctype, as_dict = 1) - lengths = {} - precisions = {} - uniques = {} + fields = self.meta.get_fieldnames_with_value(True) # optional fields like _comments - if not self.meta.istable: + if not self.meta.get('istable'): for fieldname in frappe.db.OPTIONAL_COLUMNS: - fl.append({ + fields.append({ "fieldname": fieldname, "fieldtype": "Text" }) # add _seen column if track_seen - if getattr(self.meta, 'track_seen', False): - fl.append({ + if self.meta.get('track_seen'): + fields.append({ 'fieldname': '_seen', 'fieldtype': 'Text' }) - if (not frappe.flags.in_install_db - and (frappe.flags.in_install != "frappe" - or frappe.flags.ignore_in_install)): - custom_fl = frappe.db.sql(""" - SELECT * FROM `tabCustom Field` - WHERE dt = %s AND docstatus < 2 - """, (self.doctype,), as_dict=1) - if custom_fl: fl += custom_fl - - # apply length, precision and unique from property setters - for ps in frappe.get_all("Property Setter", - fields=["field_name", "property", "value"], - filters={ - "doc_type": self.doctype, - "doctype_or_field": "DocField", - "property": ["in", ["precision", "length", "unique"]] - }): - - if ps.property=="length": - lengths[ps.field_name] = cint(ps.value) - - elif ps.property=="precision": - precisions[ps.field_name] = cint(ps.value) - - elif ps.property=="unique": - uniques[ps.field_name] = cint(ps.value) - - for f in fl: - self.columns[f['fieldname']] = DbColumn(self, - f['fieldname'], - f['fieldtype'], - lengths.get(f["fieldname"]) or f.get('length'), - f.get('default'), - f.get('search_index'), - f.get('options'), - uniques.get(f["fieldname"], - f.get('unique')), - precisions.get(f['fieldname']) or f.get('precision')) + for field in fields: + self.columns[field.get('fieldname')] = DbColumn( + self, + field.get('fieldname'), + field.get('fieldtype'), + field.get('length'), + field.get('default'), + field.get('search_index'), + field.get('options'), + field.get('unique'), + field.get('precision') + ) def validate(self): """Check if change in varchar length isn't truncating the columns""" diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 6a0d5acc37..ef84114745 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -7,6 +7,7 @@ import frappe import json from frappe import _, DoesNotExistError from frappe.boot import get_allowed_pages, get_allowed_reports +from six import string_types from frappe.cache_manager import build_domain_restriced_doctype_cache, build_domain_restriced_page_cache, build_table_count_cache class Workspace: @@ -24,7 +25,7 @@ class Workspace: self.allowed_pages = get_allowed_pages() self.allowed_reports = get_allowed_reports() - self.table_counts = build_table_count_cache() + self.table_counts = get_table_with_counts() self.restricted_doctypes = build_domain_restriced_doctype_cache() self.restricted_pages = build_domain_restriced_page_cache() @@ -109,7 +110,7 @@ class Workspace: new_data = [] for section in cards: new_items = [] - if isinstance(section.links, str): + if isinstance(section.links, string_types): links = json.loads(section.links) else: links = section.links @@ -138,12 +139,17 @@ class Workspace: return new_data def get_charts(self): + all_charts = [] if frappe.has_permission("Dashboard Chart", throw=False): charts = self.doc.charts if len(self.extended_charts): charts = charts + self.extended_charts - return [chart for chart in charts] - return [] + + for chart in charts: + chart.label = chart.label if chart.label else chart.chart_name + all_charts.append(chart) + + return all_charts def get_shortcuts(self): diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js index d4c6390cd3..a130c1d6cf 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js @@ -28,14 +28,14 @@ frappe.ui.form.on('Dashboard Chart', { 'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard', {args: values} ).then(()=> { - let dashboard_route_html = + let dashboard_route_html = `${values.dashboard}`; - let message = + let message = __(`Dashboard Chart ${values.chart_name} add to Dashboard ` + dashboard_route_html); frappe.msgprint(message); }); - + d.hide(); } }); @@ -119,15 +119,13 @@ frappe.ui.form.on('Dashboard Chart', { frm.trigger('set_chart_field_options'); } else { frappe.report_utils.get_report_filters(report_name).then(filters => { - frappe.after_ajax(()=> { - if (filters) { - frm.chart_filters = filters; - let filter_values = frappe.report_utils.get_filter_values(filters); - frm.set_value('filters_json', JSON.stringify(filter_values)); - } - frm.trigger('show_filters'); - frm.trigger('set_chart_field_options'); - }); + if (filters) { + frm.chart_filters = filters; + let filter_values = frappe.report_utils.get_filter_values(filters); + frm.set_value('filters_json', JSON.stringify(filter_values)); + } + frm.trigger('show_filters'); + frm.trigger('set_chart_field_options'); }); } @@ -140,7 +138,8 @@ frappe.ui.form.on('Dashboard Chart', { 'frappe.desk.query_report.run', { report_name: frm.doc.report_name, - filters: filters + filters: filters, + ignore_prepared_report: 1 } ).then(data => { frm.report_data = data; @@ -228,13 +227,11 @@ frappe.ui.form.on('Dashboard Chart', { show_filters: function(frm) { frm.chart_filters = []; frappe.dashboard_utils.get_filters_for_chart_type(frm.doc).then(filters => { - frappe.after_ajax(() => { if (filters) { frm.chart_filters = filters; } frm.trigger('render_filters_table'); - }); }); }, @@ -269,7 +266,7 @@ frappe.ui.form.on('Dashboard Chart', { if (filters.length > 0) { filters.forEach( filter => { - const filter_row = + const filter_row = $(` ${filter[1]} ${filter[2] || ""} @@ -295,7 +292,7 @@ frappe.ui.form.on('Dashboard Chart', { fields.map( f => { if (filters[f.fieldname]) { let condition = '='; - const filter_row = + const filter_row = $(` ${f.label} ${condition} diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index f181f6d7e4..0a017a0de2 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -31,7 +31,6 @@ "filters_json", "chart_options_section", "type", - "width", "column_break_2", "color", "section_break_10", @@ -127,13 +126,6 @@ "options": "Line\nBar\nPercentage\nPie", "reqd": 1 }, - { - "fieldname": "width", - "fieldtype": "Select", - "label": "Width", - "options": "Half\nFull", - "reqd": 1 - }, { "fieldname": "column_break_2", "fieldtype": "Column Break" @@ -223,7 +215,7 @@ } ], "links": [], - "modified": "2020-03-01 22:08:47.135523", + "modified": "2020-03-13 19:19:37.162771", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index fbacb207fe..f01c976b9c 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -274,7 +274,7 @@ def get_week_ending(date): if week_of_the_year == 52: date = add_to_date(date, years=1) # first day of next week - date = add_to_date('{}-01-01'.format(date.year), weeks = (week_of_the_year + 1)%52) + date = add_to_date('{}-01-01'.format(date.year), weeks = (week_of_the_year%52) + 1) # last day of this week return add_to_date(date, days=-1) diff --git a/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.json b/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.json index df278fb4c1..51b5ed3036 100644 --- a/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.json +++ b/frappe/desk/doctype/dashboard_chart_link/dashboard_chart_link.json @@ -1,77 +1,41 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, + "actions": [], "creation": "2019-03-12 15:00:57.052684", - "custom": 0, - "docstatus": 0, "doctype": "DocType", - "document_type": "", "editable_grid": 1, "engine": "InnoDB", + "field_order": [ + "chart", + "width" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "columns": 8, "fieldname": "chart", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Chart", - "length": 0, - "no_copy": 0, - "options": "Dashboard Chart", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "Dashboard Chart" + }, + { + "default": "Half", + "fieldname": "width", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Width", + "options": "Half\nFull" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, "istable": 1, - "max_attachments": 0, - "modified": "2019-03-12 15:01:31.639414", + "links": [], + "modified": "2020-03-13 19:23:05.561687", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart Link", - "name_case": "", "owner": "Administrator", "permissions": [], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/desk/doctype/desk_chart/desk_chart.json b/frappe/desk/doctype/desk_chart/desk_chart.json index c32e774454..c3c9231353 100644 --- a/frappe/desk/doctype/desk_chart/desk_chart.json +++ b/frappe/desk/doctype/desk_chart/desk_chart.json @@ -6,8 +6,7 @@ "engine": "InnoDB", "field_order": [ "chart_name", - "label", - "size" + "label" ], "fields": [ { @@ -23,18 +22,11 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Label" - }, - { - "fieldname": "size", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Size", - "options": "Full\nHalf" } ], "istable": 1, "links": [], - "modified": "2020-01-23 16:47:16.265651", + "modified": "2020-03-20 10:04:13.992228", "modified_by": "Administrator", "module": "Desk", "name": "Desk Chart", diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index c725ed6c37..71088f8fea 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -61,6 +61,7 @@ def setup_complete(args): stages = get_setup_stages(args) try: + frappe.flags.in_setup_wizard = True current_task = None for idx, stage in enumerate(stages): frappe.publish_realtime('setup_task', {"progress": [idx, len(stages)], @@ -75,6 +76,8 @@ def setup_complete(args): else: run_setup_success(args) return {'status': 'ok'} + finally: + frappe.flags.in_setup_wizard = False def update_global_settings(args): if args.language and args.language != "English": @@ -349,6 +352,11 @@ def email_setup_wizard_exception(traceback, args): message=message, delayed=False) +def log_setup_wizard_exception(traceback, args): + with open('../logs/setup-wizard.log', 'w+') as setup_log: + setup_log.write(traceback) + setup_log.write(json.dumps(args)) + def get_language_code(lang): return frappe.db.get_value('Language', {'language_name':lang}) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 301c37cc21..d210af02fd 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -77,9 +77,9 @@ def generate_report_result(report, filters=None, user=None): if len(res) > 5: skip_total_row = cint(res[5]) - if report.custom_columns: - columns = json.loads(report.custom_columns) - result = add_data_to_custom_columns(columns, result) + if report.custom_columns: + columns = json.loads(report.custom_columns) + result = add_data_to_custom_columns(columns, result) if result: result = get_filtered_data(report.ref_doctype, columns, result, user) diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 4393e08941..6a3dd89873 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -129,11 +129,19 @@ def get_context(context): allow_update = True if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit: allow_update = False - - if allow_update: - frappe.db.set_value(doc.doctype, doc.name, self.set_property_after_alert, - self.property_value, update_modified = False) - doc.set(self.set_property_after_alert, self.property_value) + try: + if allow_update and not doc.flags.in_notification_update: + doc.set(self.set_property_after_alert, self.property_value) + doc.flags.updater_reference = { + 'doctype': self.doctype, + 'docname': self.name, + 'label': _('via Notification') + } + doc.flags.in_notification_update = True + doc.save(ignore_permissions=True) + doc.flags.in_notification_update = False + except Exception: + frappe.log_error(title='Document update failed', message=frappe.get_traceback()) def send_an_email(self, doc, context): from email.utils import formataddr @@ -301,23 +309,23 @@ def evaluate_alert(doc, alert, event): return if event=="Value Change" and not doc.is_new(): - try: - db_value = frappe.db.get_value(doc.doctype, doc.name, alert.value_changed) - except Exception as e: - if frappe.db.is_missing_column(e): - alert.db_set('enabled', 0) - frappe.log_error('Notification {0} has been disabled due to missing field'.format(alert.name)) - return - else: - raise - db_value = parse_val(db_value) - if (doc.get(alert.value_changed) == db_value) or (not db_value and not doc.get(alert.value_changed)): - return # value not changed + if not frappe.db.has_column(doc.doctype, alert.value_changed): + alert.db_set('enabled', 0) + frappe.log_error('Notification {0} has been disabled due to missing field'.format(alert.name)) + return + + doc_before_save = doc.get_doc_before_save() + field_value_before_save = doc_before_save.get(alert.value_changed) if doc_before_save else None + + field_value_before_save = parse_val(field_value_before_save) + if (doc.get(alert.value_changed) == field_value_before_save): + # value not changed + return if event != "Value Change" and not doc.is_new(): # reload the doc for the latest values & comments, # except for validate type event. - doc = frappe.get_doc(doc.doctype, doc.name) + doc.reload() alert.send(doc) except TemplateError: frappe.throw(_("Error while evaluating Notification {0}. Please fix your template.").format(alert)) diff --git a/frappe/hooks.py b/frappe/hooks.py index 9d5389b529..c44c05fdf4 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -42,7 +42,6 @@ app_include_css = [ "assets/css/list.min.css", "assets/css/form.min.css", "assets/css/report.min.css", - "assets/css/module.min.css" ] web_include_js = [ @@ -134,12 +133,13 @@ doc_events = { ], "on_trash": [ "frappe.desk.notifications.clear_doctype_notifications", - "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions" + "frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions", + "frappe.cache_manager.build_table_count_cache" ], "on_change": [ "frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points" ], - "after_insert": "frappe.cache_manager.build_table_count_cache", + "after_insert": "frappe.cache_manager.build_table_count_cache" }, "Event": { "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_event_in_google_calendar", @@ -257,7 +257,10 @@ bot_parsers = [ 'frappe.utils.bot.CountBot' ] -setup_wizard_exception = "frappe.desk.page.setup_wizard.setup_wizard.email_setup_wizard_exception" +setup_wizard_exception = [ + "frappe.desk.page.setup_wizard.setup_wizard.email_setup_wizard_exception", + "frappe.desk.page.setup_wizard.setup_wizard.log_setup_wizard_exception" +] before_migrate = ['frappe.patches.v11_0.sync_user_permission_doctype_before_migrate.execute'] after_migrate = ['frappe.website.doctype.website_theme.website_theme.generate_theme_files_if_not_exist'] diff --git a/frappe/model/document.py b/frappe/model/document.py index 86bee6cef8..66dd7e3c58 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -268,7 +268,7 @@ class Document(BaseDocument): if hasattr(self, "__islocal"): delattr(self, "__islocal") - if not (frappe.flags.in_migrate or frappe.local.flags.in_install): + if not (frappe.flags.in_migrate or frappe.local.flags.in_install or frappe.flags.in_setup_wizard): follow_document(self.doctype, self.name, frappe.session.user) return self @@ -846,9 +846,7 @@ class Document(BaseDocument): if not self.flags.in_insert: # value change is not applicable in insert - event_map['validate'] = 'Value Change' - event_map['before_change'] = 'Value Change' - event_map['before_update_after_submit'] = 'Value Change' + event_map['on_change'] = 'Value Change' for alert in self.flags.notifications: event = event_map.get(method, None) @@ -945,7 +943,6 @@ class Document(BaseDocument): elif self._action=="update_after_submit": self.run_method("on_update_after_submit") - self.run_method('on_change') self.clear_cache() self.notify_update() @@ -955,6 +952,8 @@ class Document(BaseDocument): if getattr(self.meta, 'track_changes', False) and self._doc_before_save and not self.flags.ignore_version: self.save_version() + self.run_method('on_change') + if (self.doctype, self.name) in frappe.flags.currently_saving: frappe.flags.currently_saving.remove((self.doctype, self.name)) @@ -979,7 +978,7 @@ class Document(BaseDocument): def reset_seen(self): """Clear _seen property and set current user as seen""" if getattr(self.meta, 'track_seen', False): - self._seen = json.dumps([frappe.session.user]) + frappe.db.set_value(self.doctype, self.name, "_seen", json.dumps([frappe.session.user]), update_modified=False) def notify_update(self): """Publish realtime that the current document is modified""" diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 927a56b6b8..5065684311 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -165,7 +165,7 @@ class Meta(Document): def get_valid_columns(self): if not hasattr(self, "_valid_columns"): - if self.name in ("DocType", "DocField", "DocPerm", 'DocType Action', 'DocType Link', "Property Setter"): + if self.name in ("DocType", "DocField", "DocPerm", 'DocType Action', 'DocType Link'): self._valid_columns = get_table_columns(self.name) else: self._valid_columns = self.default_fields + \ @@ -290,17 +290,20 @@ class Meta(Document): return get_workflow_name(self.name) def add_custom_fields(self): - try: - self.extend("fields", frappe.db.sql("""SELECT * FROM `tabCustom Field` - WHERE dt = %s AND docstatus < 2""", (self.name,), as_dict=1, - update={"is_custom_field": 1})) - except Exception as e: - if frappe.db.is_table_missing(e): - return - else: - raise + if not frappe.db.table_exists('Custom Field'): + return + + custom_fields = frappe.db.sql(""" + SELECT * FROM `tabCustom Field` + WHERE dt = %s AND docstatus < 2 + """, (self.name,), as_dict=1, update={"is_custom_field": 1}) + + self.extend("fields", custom_fields) def apply_property_setters(self): + if not frappe.db.table_exists('Property Setter'): + return + property_setters = frappe.db.sql("""select * from `tabProperty Setter` where doc_type=%s""", (self.name,), as_dict=1) @@ -378,8 +381,9 @@ class Meta(Document): if custom_perms: self.permissions = [Document(d) for d in custom_perms] - def get_fieldnames_with_value(self): - return [df.fieldname for df in self.fields if df.fieldtype not in no_value_fields] + def get_fieldnames_with_value(self, with_field_meta=False): + return [df if with_field_meta else df.fieldname \ + for df in self.fields if df.fieldtype not in no_value_fields] def get_fields_to_check_permissions(self, user_permission_doctypes): @@ -529,7 +533,9 @@ def get_field_currency(df, doc=None): if currency: ref_docname = doc.name else: - currency = frappe.db.get_value(doc.parenttype, doc.parent, df.get("options")) + if frappe.get_meta(doc.parenttype).has_field(df.get("options")): + # only get_value if parent has currency field + currency = frappe.db.get_value(doc.parenttype, doc.parent, df.get("options")) if currency: frappe.local.field_currency.setdefault((doc.doctype, ref_docname), frappe._dict())\ @@ -542,7 +548,7 @@ def get_field_precision(df, doc=None, currency=None): """get precision based on DocField options and fieldvalue in doc""" from frappe.utils import get_number_format_info - if cint(df.precision): + if df.precision: precision = cint(df.precision) elif df.fieldtype == "Currency": diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 0bb9b3fb1d..b134f2f8dc 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -250,7 +250,7 @@ def print_workflow_log(messages, title, doctype, indicator): html = "
{0}
".format(doc) msg += html - frappe.msgprint(msg, title=_("Workflow Status"), indicator=indicator) + frappe.msgprint(msg, title=_("Workflow Status"), indicator=indicator, is_minimizable=True) @frappe.whitelist() def get_common_transition_actions(docs, doctype): diff --git a/frappe/patches.txt b/frappe/patches.txt index f1a9918946..a33b4d68b0 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -268,3 +268,5 @@ execute:frappe.delete_doc_if_exists('DocType', 'Google Maps Settings') execute:frappe.db.set_default('desktop:home_page', 'workspace') execute:frappe.delete_doc_if_exists('DocType', 'GSuite Settings') execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates') +execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account') +execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') diff --git a/frappe/public/build.json b/frappe/public/build.json index 9f0ed63c89..75a89e5010 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -222,6 +222,8 @@ "public/js/frappe/views/communication.js", "public/js/frappe/views/translation_manager.js", + "public/js/frappe/widgets/widget_group.js", + "public/js/frappe/ui/sort_selector.html", "public/js/frappe/ui/sort_selector.js", @@ -237,12 +239,8 @@ "public/js/frappe/utils/energy_point_utils.js", "public/js/frappe/utils/dashboard_utils.js", "public/js/frappe/ui/chart.js", - "public/js/frappe/ui/dashboard_chart.js", "public/js/frappe/barcode_scanner/index.js" ], - "css/module.min.css": [ - "public/less/module.less" - ], "css/form.min.css": [ "public/less/form_grid.less" ], diff --git a/frappe/public/css/bootstrap.css b/frappe/public/css/bootstrap.css index a8e6c87cda..16afe3cd54 100644 --- a/frappe/public/css/bootstrap.css +++ b/frappe/public/css/bootstrap.css @@ -15,7 +15,6 @@ body { } article, aside, -details, figcaption, figure, footer, @@ -24,8 +23,7 @@ hgroup, main, menu, nav, -section, -summary { +section { display: block; } audio, diff --git a/frappe/public/css/module.css b/frappe/public/css/module.css deleted file mode 100644 index 9e937b0957..0000000000 --- a/frappe/public/css/module.css +++ /dev/null @@ -1,113 +0,0 @@ -.module-head { - padding: 15px 30px; - border-bottom: 1px solid #EBEFF2; -} -.module-head h1 { - padding: 0px; - margin: 0px; -} -.module-body { - padding: 0px 15px; -} -.module-body .section-head { - margin-bottom: 15px; - margin-top: 0px; -} -.module-section { - border-bottom: 1px solid #EBEFF2; -} -.module-section .module-section-link { - line-height: 1.5em; -} -.module-section-column { - padding: 30px; -} -@media (min-width: 767px) { - .module-section:nth-child(even) { - background-color: #fafbfc; - } - .module-section:last-child { - border-bottom: none; - } -} -@media (max-width: 991px) { - .module-body { - margin-top: 15px; - border-top: 1px solid #d1d8dd; - } -} -@media (max-width: 767px) { - .module-body { - margin-top: 0; - border-top: 1px solid transparent; - } -} -@media (max-width: 767px) { - .module-section { - border: none; - } - .module-section-column { - border-bottom: 1px solid #EBEFF2; - } - .module-section-column:nth-child(even) { - background-color: #fafbfc; - } - .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 #d1d8dd; - 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: #F7FAFC; -} -.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: 767px) { - body[data-route^="Module"] .page-title { - width: 100%; - } - body[data-route^="Module"] .page-actions { - display: none !important; - } - body[data-route^="Module"] .layout-main-section { - border-bottom: 0px; - } -} diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index e7f5f4023f..593f987a9a 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -560,12 +560,18 @@ frappe.ui.form.Timeline = class Timeline { return; } - let data_import_link = frappe.utils.get_form_link( - 'Data Import Beta', - data.data_import, - true, - __('via Data Import') - ); + let updater_reference_link = null; + + if (!$.isEmptyObject(data.updater_reference)) { + let label = updater_reference.label || __('via {0}', [updater_reference.doctype]); + let updater_reference = data.updater_reference; + updater_reference_link = frappe.utils.get_form_link( + updater_reference.doctype, + updater_reference.docname, + true, + label + ); + } // value changed in parent if (data.changed && data.changed.length) { @@ -573,13 +579,13 @@ frappe.ui.form.Timeline = class Timeline { data.changed.every(function(p) { if (p[0]==='docstatus') { if (p[2]==1) { - let message = data.data_import - ? __('submitted this document {0}', [data_import_link]) + let message = updater_reference_link + ? __('submitted this document {0}', [updater_reference_link]) : __('submitted this document'); out.push(me.get_version_comment(version, message)); } else if (p[2]==2) { - let message = data.data_import - ? __('cancelled this document {0}', [data_import_link]) + let message = updater_reference_link + ? __('cancelled this document {0}', [updater_reference_link]) : __('cancelled this document'); out.push(me.get_version_comment(version, message)); } @@ -600,10 +606,10 @@ frappe.ui.form.Timeline = class Timeline { } return parts.length < 3; }); - if(parts.length) { + if (parts.length) { let message; - if (data.data_import) { - message = __("changed value of {0} {1}", [parts.join(', ').bold(), data_import_link]); + if (updater_reference_link) { + message = __("changed value of {0} {1}", [parts.join(', ').bold(), updater_reference_link]); } else { message = __("changed value of {0}", [parts.join(', ').bold()]); } @@ -638,10 +644,10 @@ frappe.ui.form.Timeline = class Timeline { }); return parts.length < 3; }); - if(parts.length) { + if (parts.length) { let message; - if (data.data_import) { - message = __("changed values for {0} {1}", [parts.join(', '), data_import_link]); + if (updater_reference_link) { + message = __("changed values for {0} {1}", [parts.join(', '), updater_reference_link]); } else { message = __("changed values for {0}", [parts.join(', ')]); } diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index cc739e79d5..0e36e671cc 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -22,6 +22,9 @@ export default class GridRow { if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) { // pass } else { + if (!me.grid.is_editable()) { + me.docfields.map(df => df.read_only = 1); + } me.toggle_view(); return false; } diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index 5034b8969c..02caf25557 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -32,7 +32,9 @@ frappe.ui.form.Sidebar = Class.extend({ this.make_tags(); this.make_like(); - this.make_follow(); + if (frappe.boot.user.document_follow_notify) { + this.make_follow(); + } this.bind_events(); this.setup_keyboard_shortcuts(); @@ -74,7 +76,9 @@ frappe.ui.form.Sidebar = Class.extend({ this.frm.assign_to.refresh(); this.frm.attachments.refresh(); this.frm.shared.refresh(); - this.frm.follow.refresh(); + if (frappe.boot.user.document_follow_notify) { + this.frm.follow.refresh(); + } 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}", diff --git a/frappe/public/js/frappe/form/templates/timeline.html b/frappe/public/js/frappe/form/templates/timeline.html index ead3bdb870..5600f9384f 100644 --- a/frappe/public/js/frappe/form/templates/timeline.html +++ b/frappe/public/js/frappe/form/templates/timeline.html @@ -17,7 +17,7 @@ {% } %} {% } %}
-
+
diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html index f8e868da20..880a91cf81 100644 --- a/frappe/public/js/frappe/list/list_sidebar.html +++ b/frappe/public/js/frappe/list/list_sidebar.html @@ -10,6 +10,7 @@