Merge branch 'develop' into patch-attach-develop
This commit is contained in:
commit
816d23a1fa
18 changed files with 107 additions and 198 deletions
|
|
@ -7,7 +7,7 @@ addons:
|
|||
- test_site_producer
|
||||
mariadb: 10.3
|
||||
postgresql: 9.5
|
||||
chrome: stable
|
||||
firefox: latest
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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: <b>Child 1 of DocType for Import</b> Row #1: Value missing for: Child Title"
|
||||
self.assertEqual(frappe.parse_json(import_log[0]['messages'][0])['message'], expected_error)
|
||||
expected_error = "Error: <b>Child 1 of DocType for Import</b> 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'], "<b>Child Title (Table Field 1)</b> is a mandatory field")
|
||||
|
||||
self.assertEqual(warnings[1]['row'], 3)
|
||||
self.assertEqual(warnings[1]['message'], "<b>Child Title (Table Field 1 Again)</b> is a mandatory field")
|
||||
|
||||
self.assertEqual(warnings[2]['row'], 4)
|
||||
self.assertEqual(warnings[2]['message'], "<b>Title</b> 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
frappe.db.set_value('Notification Settings', user, 'seen', value, update_modified=False)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 = $(`<div class="${this.current_theme == theme.name ? "selected" : "" }">
|
||||
<div data-theme=${theme.name}>
|
||||
|
|
@ -69,15 +68,9 @@ frappe.ui.ThemeSwitcher = class ThemeSwitcher {
|
|||
</div>
|
||||
</div>`);
|
||||
|
||||
// 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();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
--checkbox-right-margin: 8px;
|
||||
|
||||
.label-area {
|
||||
line-height: 1;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
@import "variables";
|
||||
@import "css_variables";
|
||||
@import "../common/mixins.scss";
|
||||
@import "../common/global.scss";
|
||||
@import "../common/icons.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);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
@import '~quill/dist/quill.core';
|
||||
@import 'variables';
|
||||
@import 'css_variables';
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
@import "../common/mixins";
|
||||
@import "../common/global";
|
||||
|
|
|
|||
|
|
@ -131,5 +131,6 @@ $spacers: (
|
|||
@import "~bootstrap/scss/functions";
|
||||
@import "~bootstrap/scss/variables";
|
||||
@import "~bootstrap/scss/mixins";
|
||||
@import 'css_variables';
|
||||
|
||||
$code-color: $purple;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -1,65 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<!-- Chrome, Firefox OS and Opera -->
|
||||
<meta name="theme-color" content="#0089FF">
|
||||
<!-- Windows Phone -->
|
||||
<meta name="msapplication-navbutton-color" content="#0089FF">
|
||||
<!-- iOS Safari -->
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="#0089FF">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta content="utf-8" http-equiv="encoding">
|
||||
<meta name="author" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0,
|
||||
maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="white">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>Frappe</title>
|
||||
<link rel="shortcut icon"
|
||||
href="{{ favicon or "/assets/frappe/images/frappe-favicon.svg" }}" type="image/x-icon">
|
||||
<link rel="icon"
|
||||
href="{{ favicon or "/assets/frappe/images/frappe-favicon.svg" }}" type="image/x-icon">
|
||||
{% for include in include_css -%}
|
||||
<link type="text/css" rel="stylesheet" href="{{ include }}?ver={{ build_version }}">
|
||||
{%- endfor -%}
|
||||
</head>
|
||||
<body data-theme="{{ desk_theme.lower() }}">
|
||||
{% include "public/icons/timeless/symbol-defs.svg" %}
|
||||
<div class="centered splash">
|
||||
<img src="{{ splash_image or "/assets/frappe/images/frappe-framework-logo.png" }}"
|
||||
style="max-width: 100px; max-height: 100px;">
|
||||
</div>
|
||||
<div class="main-section">
|
||||
<header></header>
|
||||
<div id="body"></div>
|
||||
<footer></footer>
|
||||
</div>
|
||||
<html data-theme="{{ desk_theme.lower() }}">
|
||||
<head>
|
||||
<!-- Chrome, Firefox OS and Opera -->
|
||||
<meta name="theme-color" content="#0089FF">
|
||||
<!-- Windows Phone -->
|
||||
<meta name="msapplication-navbutton-color" content="#0089FF">
|
||||
<!-- iOS Safari -->
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="#0089FF">
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||
<meta content="utf-8" http-equiv="encoding">
|
||||
<meta name="author" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0,
|
||||
maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="white">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<title>Frappe</title>
|
||||
<link rel="shortcut icon"
|
||||
href="{{ favicon or "/assets/frappe/images/frappe-favicon.svg" }}" type="image/x-icon">
|
||||
<link rel="icon"
|
||||
href="{{ favicon or "/assets/frappe/images/frappe-favicon.svg" }}" type="image/x-icon">
|
||||
{% for include in include_css -%}
|
||||
<link type="text/css" rel="stylesheet" href="{{ include }}?ver={{ build_version }}">
|
||||
{%- endfor -%}
|
||||
</head>
|
||||
<body>
|
||||
{% include "public/icons/timeless/symbol-defs.svg" %}
|
||||
<div class="centered splash">
|
||||
<img src="{{ splash_image or "/assets/frappe/images/frappe-framework-logo.png" }}"
|
||||
style="max-width: 100px; max-height: 100px;">
|
||||
</div>
|
||||
<div class="main-section">
|
||||
<header></header>
|
||||
<div id="body"></div>
|
||||
<footer></footer>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
window._version_number = "{{ build_version }}";
|
||||
// browser support
|
||||
window.app = true;
|
||||
window.dev_server = {{ dev_server }};
|
||||
<script type="text/javascript">
|
||||
window._version_number = "{{ build_version }}";
|
||||
// browser support
|
||||
window.app = true;
|
||||
window.dev_server = {{ dev_server }};
|
||||
|
||||
if(!window.frappe) window.frappe = {};
|
||||
if(!window.frappe) window.frappe = {};
|
||||
|
||||
frappe.boot = {{ boot }};
|
||||
frappe.boot = {{ boot }};
|
||||
|
||||
frappe.csrf_token = "{{ csrf_token }}";
|
||||
frappe.csrf_token = "{{ csrf_token }}";
|
||||
|
||||
</script>
|
||||
</script>
|
||||
|
||||
{% for include in include_js %}
|
||||
<script type="text/javascript" src="{{ include }}?ver={{ build_version }}"></script>
|
||||
{% endfor %}
|
||||
{% include "templates/includes/app_analytics/google_analytics.html" %}
|
||||
{% include "templates/includes/app_analytics/mixpanel_analytics.html" %}
|
||||
{% for include in include_js %}
|
||||
<script type="text/javascript" src="{{ include }}?ver={{ build_version }}"></script>
|
||||
{% endfor %}
|
||||
{% include "templates/includes/app_analytics/google_analytics.html" %}
|
||||
{% include "templates/includes/app_analytics/mixpanel_analytics.html" %}
|
||||
|
||||
{% for sound in (sounds or []) %}
|
||||
<audio preload="auto" id="sound-{{ sound.name }}" volume={{ sound.volume or 1 }}>
|
||||
<source src="{{ sound.src }}"></source>
|
||||
</audio>
|
||||
{% endfor %}
|
||||
</body>
|
||||
{% for sound in (sounds or []) %}
|
||||
<audio preload="auto" id="sound-{{ sound.name }}" volume={{ sound.volume or 1 }}>
|
||||
<source src="{{ sound.src }}"></source>
|
||||
</audio>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue