diff --git a/.travis.yml b/.travis.yml index 53ad56a948..ffada0286f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ addons: - test_site_producer mariadb: 10.3 postgresql: 9.5 - chrome: stable + firefox: latest services: - xvfb diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index e9fa7217a8..13c6ca812f 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -578,7 +578,7 @@ def run_ui_tests(context, app, headless=False): frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile") # run for headless mode - run_or_open = 'run --browser chrome --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open' + run_or_open = 'run --browser firefox --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open' command = '{site_env} {password_env} {cypress} {run_or_open}' formatted_command = command.format(site_env=site_env, password_env=password_env, cypress=cypress_path, run_or_open=run_or_open) diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py index dde3dfaee9..388d9389f2 100644 --- a/frappe/core/doctype/data_import/importer.py +++ b/frappe/core/doctype/data_import/importer.py @@ -472,32 +472,6 @@ class ImportFile: doc = parent_doc - if self.import_type == INSERT: - # check if there is atleast one row for mandatory table fields - meta = frappe.get_meta(self.doctype) - mandatory_table_fields = [ - df - for df in meta.fields - if df.fieldtype in table_fieldtypes - and df.reqd - and len(doc.get(df.fieldname, [])) == 0 - ] - if len(mandatory_table_fields) == 1: - self.warnings.append( - { - "row": first_row.row_number, - "message": _("There should be atleast one row for {0} table").format( - frappe.bold(mandatory_table_fields[0].label) - ), - } - ) - elif mandatory_table_fields: - fields_string = ", ".join([df.label for df in mandatory_table_fields]) - message = _("There should be atleast one row for the following tables: {0}").format( - fields_string - ) - self.warnings.append({"row": first_row.row_number, "message": message}) - return doc, rows, data[len(rows) :] def get_warnings(self): @@ -626,7 +600,6 @@ class Row: new_doc.update(doc) doc = new_doc - self.check_mandatory_fields(doctype, doc, table_df) return doc def validate_value(self, value, col): @@ -727,66 +700,6 @@ class Row: pass return value - def check_mandatory_fields(self, doctype, doc, table_df=None): - """If import type is Insert: - Check for mandatory fields (except table fields) in doc - if import type is Update: - Check for name field or autoname field in doc - """ - meta = frappe.get_meta(doctype) - if self.import_type == UPDATE: - if meta.istable: - # when updating records with table rows, - # there are two scenarios: - # 1. if row 'name' is provided in the template - # the table row will be updated - # 2. if row 'name' is not provided - # then a new row will be added - # so we dont need to check for mandatory - return - - # for update, only ID (name) field is mandatory - id_field = get_id_field(doctype) - if doc.get(id_field.fieldname) in INVALID_VALUES: - self.warnings.append( - { - "row": self.row_number, - "message": _("{0} is a mandatory field").format(id_field.label), - } - ) - return - - fields = [ - df - for df in meta.fields - if df.fieldtype not in table_fieldtypes - and df.reqd - and doc.get(df.fieldname) in INVALID_VALUES - ] - - if not fields: - return - - def get_field_label(df): - return "{0}{1}".format(df.label, " ({})".format(table_df.label) if table_df else "") - - if len(fields) == 1: - field_label = get_field_label(fields[0]) - self.warnings.append( - { - "row": self.row_number, - "message": _("{0} is a mandatory field").format(frappe.bold(field_label)), - } - ) - else: - fields_string = ", ".join([frappe.bold(get_field_label(df)) for df in fields]) - self.warnings.append( - { - "row": self.row_number, - "message": _("{0} are mandatory fields").format(fields_string), - } - ) - def get_values(self, indexes): return [self.data[i] for i in indexes] diff --git a/frappe/core/doctype/data_import/test_importer.py b/frappe/core/doctype/data_import/test_importer.py index b083b9eaaa..f76d4504a4 100644 --- a/frappe/core/doctype/data_import/test_importer.py +++ b/frappe/core/doctype/data_import/test_importer.py @@ -13,7 +13,7 @@ doctype_name = 'DocType for Import' class TestImporter(unittest.TestCase): @classmethod def setUpClass(cls): - create_doctype_if_not_exists(doctype_name) + create_doctype_if_not_exists(doctype_name,) def test_data_import_from_file(self): import_file = get_import_file('sample_import_file') @@ -59,18 +59,18 @@ class TestImporter(unittest.TestCase): def test_data_import_without_mandatory_values(self): import_file = get_import_file('sample_import_file_without_mandatory') data_import = self.get_importer(doctype_name, import_file) + frappe.local.message_log = [] data_import.start_import() data_import.reload() - warnings = frappe.parse_json(data_import.template_warnings) + import_log = frappe.parse_json(data_import.import_log) + self.assertEqual(import_log[0]['row_indexes'], [2,3]) + expected_error = "Error: Child 1 of DocType for Import Row #1: Value missing for: Child Title" + self.assertEqual(frappe.parse_json(import_log[0]['messages'][0])['message'], expected_error) + expected_error = "Error: Child 1 of DocType for Import Row #2: Value missing for: Child Title" + self.assertEqual(frappe.parse_json(import_log[0]['messages'][1])['message'], expected_error) - self.assertEqual(warnings[0]['row'], 2) - self.assertEqual(warnings[0]['message'], "Child Title (Table Field 1) is a mandatory field") - - self.assertEqual(warnings[1]['row'], 3) - self.assertEqual(warnings[1]['message'], "Child Title (Table Field 1 Again) is a mandatory field") - - self.assertEqual(warnings[2]['row'], 4) - self.assertEqual(warnings[2]['message'], "Title is a mandatory field") + self.assertEqual(import_log[1]['row_indexes'], [4]) + self.assertEqual(frappe.parse_json(import_log[1]['messages'][0])['message'], "Title is required") def test_data_import_update(self): existing_doc = frappe.get_doc( @@ -104,6 +104,8 @@ class TestImporter(unittest.TestCase): data_import.reference_doctype = doctype data_import.import_file = import_file.file_url data_import.insert() + # Commit so that the first import failure does not rollback the Data Import insert. + frappe.db.commit() return data_import diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 3f19a6ef7b..0e7b9d43a7 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -8,7 +8,7 @@ from frappe.utils import cint, flt, has_gravatar, escape_html, format_datetime, from frappe import throw, msgprint, _ from frappe.utils.password import update_password as _update_password, check_password from frappe.desk.notifications import clear_notifications -from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings +from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings, toggle_notifications from frappe.utils.user import get_system_managers from bs4 import BeautifulSoup import frappe.permissions @@ -146,6 +146,9 @@ class User(Document): if not cint(self.enabled) and getattr(frappe.local, "login_manager", None): frappe.local.login_manager.logout(user=self.name) + # toggle notifications based on the user's status + toggle_notifications(self.name, enable=cint(self.enabled)) + def add_system_manager_role(self): # if adding system manager, do nothing if not cint(self.enabled) or ("System Manager" in [user_role.role for user_role in @@ -358,6 +361,9 @@ class User(Document): set `user`=null where `user`=%s""", (self.name)) + # delete notification settings + frappe.delete_doc("Notification Settings", self.name, ignore_permissions=True) + def before_rename(self, old_name, new_name, merge=False): self.check_demo() diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 34726bdf8a..4ab40bffe9 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -44,6 +44,11 @@ def create_notification_settings(user): _doc.insert(ignore_permissions=True) +def toggle_notifications(user, enable=False): + if frappe.db.exists("Notification Settings", user): + frappe.db.set_value("Notification Settings", user, 'enabled', enable) + + @frappe.whitelist() def get_subscribed_documents(): if not frappe.session.user: @@ -75,4 +80,4 @@ def get_permission_query_conditions(user): @frappe.whitelist() def set_seen_value(value, user): - frappe.db.set_value('Notification Settings', user, 'seen', value, update_modified=False) \ No newline at end of file + frappe.db.set_value('Notification Settings', user, 'seen', value, update_modified=False) diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 9ec7b0e931..7167fc3982 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -621,6 +621,7 @@ $.extend(frappe.model, { r.message || args.new_name]); if(locals[doctype] && locals[doctype][docname]) delete locals[doctype][docname]; + this.frm.reload_doc(); d.hide(); if(callback) callback(r.message); diff --git a/frappe/public/js/frappe/ui/theme_switcher.js b/frappe/public/js/frappe/ui/theme_switcher.js index 31baf697f0..317198bca5 100644 --- a/frappe/public/js/frappe/ui/theme_switcher.js +++ b/frappe/public/js/frappe/ui/theme_switcher.js @@ -14,7 +14,7 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { } refresh() { - this.current_theme = document.body.dataset.theme; + this.current_theme = document.documentElement.getAttribute("data-theme") || "light"; this.fetch_themes().then(() => { this.render(); }); @@ -45,7 +45,6 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { }); } - get_preview_html(theme) { const preview = $(`
@@ -69,15 +68,9 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher {
`); - // preview.on('mouseover', () => { - // this.toggle_theme(theme.name, true) - // }) - - // preview.on('mouseleave', () => { - // this.toggle_theme(this.current_theme, true) - // }) - preview.on('click', () => { + if (this.current_theme === theme.name) return; + this.themes.forEach((th) => { th.$html.removeClass("selected"); }); @@ -89,19 +82,15 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { return preview; } - toggle_theme(theme, preview=false) { - if (!preview) { - document.body.dataset.theme = theme.toLowerCase(); - frappe.show_alert("Theme Changed", 3); + toggle_theme(theme) { + this.current_theme = theme.toLowerCase(); + document.documentElement.setAttribute("data-theme", this.current_theme); + frappe.show_alert("Theme Changed", 3); - frappe.call('frappe.core.doctype.user.user.switch_theme', { - theme: toTitle(theme) - }); - } else { - document.body.dataset.theme = theme.toLowerCase(); - } + frappe.xcall("frappe.core.doctype.user.user.switch_theme", { + theme: toTitle(theme) + }); } - show() { this.dialog.show(); } @@ -109,4 +98,4 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher { hide() { this.dialog.hide(); } -}; \ No newline at end of file +}; diff --git a/frappe/public/js/frappe/views/formview.js b/frappe/public/js/frappe/views/formview.js index 5d2d4ec303..6708a6ffb1 100644 --- a/frappe/public/js/frappe/views/formview.js +++ b/frappe/public/js/frappe/views/formview.js @@ -85,7 +85,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory { if (name && name.substr(0, 3) === 'new') { this.render_new_doc(doctype, name, doctype_layout); } else { - frappe.show_not_found(route); + frappe.show_not_found(); } return; } diff --git a/frappe/public/js/frappe/views/pageview.js b/frappe/public/js/frappe/views/pageview.js index 6fd91d73c4..705d13b7f0 100644 --- a/frappe/public/js/frappe/views/pageview.js +++ b/frappe/public/js/frappe/views/pageview.js @@ -73,7 +73,6 @@ frappe.views.Page = class Page { return; } this.wrapper = frappe.container.add_page(this.name); - this.wrapper.label = this.pagedoc.title || this.pagedoc.name; this.wrapper.page_name = this.pagedoc.name; // set content, script and style diff --git a/frappe/public/scss/common/global.scss b/frappe/public/scss/common/global.scss index 91cc31c50d..20778176d4 100644 --- a/frappe/public/scss/common/global.scss +++ b/frappe/public/scss/common/global.scss @@ -8,7 +8,6 @@ --checkbox-right-margin: 8px; .label-area { - line-height: 1; font-size: var(--text-sm); } diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index f4d6384352..b145879e1f 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -3,19 +3,6 @@ html { background-color: var(--bg-color); } -// transition -* { - transition: background-color 0.5s, background 0.5s; -} - -a, -.badge { - transition: 0.2s; -} - -.btn { - transition: background-color 0.2s, background 0.2s; -} body { diff --git a/frappe/public/scss/desk/index.scss b/frappe/public/scss/desk/index.scss index 1643034322..5f5fef251b 100644 --- a/frappe/public/scss/desk/index.scss +++ b/frappe/public/scss/desk/index.scss @@ -1,5 +1,4 @@ @import "variables"; -@import "css_variables"; @import "../common/mixins.scss"; @import "../common/global.scss"; @import "../common/icons.scss"; diff --git a/frappe/public/scss/desk/variables.scss b/frappe/public/scss/desk/variables.scss index 57a117cd31..2855277ccd 100644 --- a/frappe/public/scss/desk/variables.scss +++ b/frappe/public/scss/desk/variables.scss @@ -95,6 +95,10 @@ $btn-active-box-shadow: var(--shadow-inset); $mark-bg: #FDF9AF; $mark-padding: 0; +// transitions +$btn-transition: none; +$input-transition: none; + // popover $enable-shadows: true; $popover-border-radius: var(--border-radius); diff --git a/frappe/public/scss/website/index.scss b/frappe/public/scss/website/index.scss index 88f3b1db73..94b1249de1 100644 --- a/frappe/public/scss/website/index.scss +++ b/frappe/public/scss/website/index.scss @@ -1,6 +1,5 @@ @import '~quill/dist/quill.core'; @import 'variables'; -@import 'css_variables'; @import '~bootstrap/scss/bootstrap'; @import "../common/mixins"; @import "../common/global"; diff --git a/frappe/public/scss/website/variables.scss b/frappe/public/scss/website/variables.scss index fa68b57ad6..293d02b06d 100644 --- a/frappe/public/scss/website/variables.scss +++ b/frappe/public/scss/website/variables.scss @@ -131,5 +131,6 @@ $spacers: ( @import "~bootstrap/scss/functions"; @import "~bootstrap/scss/variables"; @import "~bootstrap/scss/mixins"; +@import 'css_variables'; $code-color: $purple; diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 21a0e24598..e9425cec86 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -319,7 +319,10 @@ def send_summary(timespan): from_date = getdate(from_date) to_date = getdate() - all_users = [user.email for user in get_enabled_system_users()] + + # select only those users that have energy point email notifications enabled + all_users = [user.email for user in get_enabled_system_users() if + is_email_notifications_enabled_for_type(user.name, 'Energy Point')] frappe.sendmail( subject = '{} energy points summary'.format(timespan), diff --git a/frappe/www/app.html b/frappe/www/app.html index 715ddaf409..8da4d11c00 100644 --- a/frappe/www/app.html +++ b/frappe/www/app.html @@ -1,65 +1,67 @@ - - - - - - - - - - - - - - - Frappe - - - {% for include in include_css -%} - - {%- endfor -%} - - - {% include "public/icons/timeless/symbol-defs.svg" %} -
- -
-
-
-
- -
+ + + + + + + + + + + + + + + + Frappe + + + {% for include in include_css -%} + + {%- endfor -%} + + + {% include "public/icons/timeless/symbol-defs.svg" %} +
+ +
+
+
+
+ +
- + - + - {% for include in include_js %} - - {% endfor %} - {% include "templates/includes/app_analytics/google_analytics.html" %} - {% include "templates/includes/app_analytics/mixpanel_analytics.html" %} + {% for include in include_js %} + + {% endfor %} + {% include "templates/includes/app_analytics/google_analytics.html" %} + {% include "templates/includes/app_analytics/mixpanel_analytics.html" %} - {% for sound in (sounds or []) %} - - {% endfor %} - + {% for sound in (sounds or []) %} + + {% endfor %} + +