diff --git a/.travis.yml b/.travis.yml
index e9c2ee5262..174f92ea11 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: python
-dist: trusty
+dist: bionic
addons:
hosts:
@@ -9,6 +9,10 @@ addons:
postgresql: 9.5
chrome: stable
+services:
+ - xvfb
+ - mysql
+
git:
depth: 1
@@ -23,18 +27,18 @@ cache:
matrix:
include:
- - name: "Python 3.6 MariaDB"
- python: 3.6
+ - name: "Python 3.7 MariaDB"
+ python: 3.7
env: DB=mariadb TYPE=server
script: bench --site test_site run-tests --coverage
- - name: "Python 3.6 PostgreSQL"
- python: 3.6
+ - name: "Python 3.7 PostgreSQL"
+ python: 3.7
env: DB=postgres TYPE=server
script: bench --site test_site run-tests --coverage
- name: "Cypress"
- python: 3.6
+ python: 3.7
env: DB=mariadb TYPE=ui
before_script:
- bench --site test_site execute frappe.utils.install.complete_setup_wizard
diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py
index 7ddb3d98f0..417ef2ba82 100644
--- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py
+++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py
@@ -7,7 +7,7 @@ import frappe
from frappe import _
import datetime
import json
-from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
+from frappe.utils.dashboard import cache_source, get_from_date_from_timespan
from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate, get_datetime
from frappe.model.naming import append_number_if_name_exists
from frappe.boot import get_allowed_reports
diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py
index c857bd077f..6917ef0426 100644
--- a/frappe/desk/page/setup_wizard/install_fixtures.py
+++ b/frappe/desk/page/setup_wizard/install_fixtures.py
@@ -6,12 +6,14 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes
+from frappe.utils.dashboard import sync_dashboards
def install():
update_genders()
update_salutations()
update_global_search_doctypes()
setup_email_linking()
+ sync_dashboards()
@frappe.whitelist()
def update_genders():
@@ -35,4 +37,3 @@ def setup_email_linking():
"email_id": "email_linking@example.com",
})
doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
-
\ No newline at end of file
diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py
index f44c6e775a..43c4bb8333 100644
--- a/frappe/email/test_email_body.py
+++ b/frappe/email/test_email_body.py
@@ -75,22 +75,6 @@ This is the text version of this email
else:
self.assertTrue(True)
- def test_rfc_5322_header_is_wrapped_at_998_chars(self):
- # unfortunately the db can only hold 140 chars so this can't be tested properly. test at max chars anyway.
- email = get_email_queue(
- recipients=['test@example.com'],
- sender='me@example.com',
- subject='Test Subject',
- content='
Whatever
',
- text_content='whatever',
- message_id="a.really.long.message.id.that.should.not.wrap.until.998.if.it.does.then.exchange.will.break" +
- ".really.long.message.id.that.should.not.wrap.unti")
- result = safe_decode(prepare_message(email=email, recipient='test@test.com',
- recipients_list=[]))
- self.assertTrue(
- "a.really.long.message.id.that.should.not.wrap.until.998.if.it.does.then.exchange.will.break" +
- ".really.long.message.id.that.should.not.wrap.unti" in result)
-
def test_image(self):
img_signature = '''
Content-Type: image/png
diff --git a/frappe/migrate.py b/frappe/migrate.py
index 043b6817d7..094abbe099 100644
--- a/frappe/migrate.py
+++ b/frappe/migrate.py
@@ -10,6 +10,7 @@ import frappe.translate
import frappe.modules.patch_handler
import frappe.model.sync
from frappe.utils.fixtures import sync_fixtures
+from frappe.utils.dashboard import sync_dashboards
from frappe.cache_manager import clear_global_cache
from frappe.desk.notifications import clear_notifications
from frappe.website import render
@@ -23,6 +24,7 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False):
- run before migrate hooks
- run patches
- sync doctypes (schema)
+ - sync dashboards
- sync fixtures
- sync desktop icons
- sync web pages (from /www)
@@ -53,6 +55,7 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False):
frappe.translate.clear_cache()
sync_jobs()
sync_fixtures()
+ sync_dashboards()
sync_customizations()
sync_languages()
diff --git a/frappe/model/sync.py b/frappe/model/sync.py
index c2acb59f63..320cc24677 100644
--- a/frappe/model/sync.py
+++ b/frappe/model/sync.py
@@ -45,6 +45,9 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe
("data_migration", "data_migration_mapping"),
("data_migration", "data_migration_plan_mapping"),
("data_migration", "data_migration_plan"),
+ ("desk", "number_card"),
+ ("desk", "dashboard_chart"),
+ ("desk", "dashboard"),
("desk", "onboarding_permission"),
("desk", "onboarding_step"),
("desk", "onboarding_step_map"),
diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js
index a6c15134df..78305edd5d 100644
--- a/frappe/public/js/frappe/widgets/onboarding_widget.js
+++ b/frappe/public/js/frappe/widgets/onboarding_widget.js
@@ -173,21 +173,26 @@ export default class OnboardingWidget extends Widget {
frappe.ui.form.make_quick_entry(
step.reference_document,
() => {
- if (frappe.get_route_str != current_route) {
- let args = {};
- args.message = __("Let's take you back to onboarding");
- args.title = __("Looks Great");
- args.primary_action = {
- action: () => {
- frappe.set_route(current_route).then(() => {
- this.mark_complete(step);
- });
- },
- label: __("Continue"),
- };
+ if (frappe.get_route_str() != current_route) {
+ let success_dialog = frappe.msgprint({
+ message: __("Let's take you back to onboarding"),
+ title: __("Looks Great"),
+ primary_action: {
+ action: () => {
+ success_dialog.hide();
+ frappe.set_route(current_route).then(() => {
+ this.mark_complete(step);
+ });
+ },
+ label: __("Continue"),
+ }
+ });
- frappe.msgprint(args);
- frappe.msg_dialog.custom_onhide = () => args.primary_action.action();
+ frappe.msg_dialog.custom_onhide = () => {
+ frappe.set_route(current_route).then(() => {
+ this.mark_complete(step);
+ });
+ };
} else {
this.mark_complete(step);
}
@@ -277,25 +282,27 @@ export default class OnboardingWidget extends Widget {
`);
- let success_dialog = new frappe.ui.Dialog({
- primary_action: () => {
- success_dialog.hide();
- // Wait for modal to close before removing widget
- setTimeout(() => {
- this.delete();
- }, 300);
- },
- primary_action_label: __("Continue"),
- });
+ if (!this.success_dialog) {
+ this.success_dialog = new frappe.ui.Dialog({
+ primary_action: () => {
+ this.success_dialog.hide();
+ // Wait for modal to close before removing widget
+ setTimeout(() => {
+ this.delete();
+ }, 300);
+ },
+ primary_action_label: __("Continue"),
+ });
- success_dialog.set_title(__("Onboarding Complete"));
- success_dialog.header
- .find(".indicator")
- .removeClass("hidden")
- .addClass("green");
+ this.success_dialog.set_title(__("Onboarding Complete"));
+ this.success_dialog.header
+ .find(".indicator")
+ .removeClass("hidden")
+ .addClass("green");
- success.appendTo(success_dialog.$body);
- success_dialog.show();
+ success.appendTo(this.success_dialog.$body);
+ this.success_dialog.show();
+ }
}
set_body() {
diff --git a/frappe/templates/includes/search_template.html b/frappe/templates/includes/search_template.html
index 8ad73f836a..91a9a1dc47 100644
--- a/frappe/templates/includes/search_template.html
+++ b/frappe/templates/includes/search_template.html
@@ -28,7 +28,7 @@
{% if frappe.form_dict.scope %}
-
+
{% endif %}
diff --git a/frappe/tests/test_form_load.py b/frappe/tests/test_form_load.py
index 5e8ad26b5e..34fc58465e 100644
--- a/frappe/tests/test_form_load.py
+++ b/frappe/tests/test_form_load.py
@@ -79,6 +79,8 @@ class TestFormLoad(unittest.TestCase):
user.remove_roles('Blogger', 'Website Manager')
user.add_roles(*user_roles)
+ blog_doc.delete()
+
def test_fieldlevel_permissions_in_load_for_child_table(self):
contact = frappe.new_doc('Contact')
contact.first_name = '_Test Contact 1'
diff --git a/frappe/core/page/dashboard/dashboard.py b/frappe/utils/dashboard.py
similarity index 68%
rename from frappe/core/page/dashboard/dashboard.py
rename to frappe/utils/dashboard.py
index 17f46a24d2..7cc97ed3f9 100644
--- a/frappe/core/page/dashboard/dashboard.py
+++ b/frappe/utils/dashboard.py
@@ -1,11 +1,11 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
-import json
import frappe
from frappe import _
from functools import wraps
from frappe.utils import add_to_date, get_link_to_form
+from frappe.modules.import_file import import_doc
def cache_source(function):
@@ -72,3 +72,42 @@ def get_from_date_from_timespan(to_date, timespan):
years = -50
return add_to_date(to_date, years=years, months=months, days=days,
as_datetime=True)
+
+def sync_dashboards(app=None):
+ """Import, overwrite fixtures from `[app]/fixtures`"""
+ if app:
+ apps = [app]
+ else:
+ apps = frappe.get_installed_apps()
+
+ for app_name in apps:
+ print("Updating Dashboard for {app}".format(app=app_name))
+ for module_name in frappe.local.app_modules.get(app_name) or []:
+ config = get_config(app_name, module_name)
+ if config:
+ frappe.flags.in_import = True
+ make_records(config.charts, "Dashboard Chart")
+ make_records(config.number_cards, "Number Card")
+ make_records(config.dashboards, "Dashboard")
+ frappe.flags.in_import = False
+
+def make_records(config, doctype):
+ if not config:
+ return
+
+ try:
+ for item in config:
+ item["doctype"] = doctype
+ import_doc(item)
+ frappe.db.commit()
+ except frappe.DuplicateEntryError:
+ pass
+
+def get_config(app, module):
+ try:
+ module_dashboards = frappe.get_module('{app}.{module}.dashboard_fixtures'.format(app=app, module=module))
+ if hasattr(module_dashboards, 'get_data'):
+ return frappe._dict(module_dashboards.get_data())
+ return None
+ except ImportError:
+ return None
diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py
index f80d819907..daae75228e 100644
--- a/frappe/utils/safe_exec.py
+++ b/frappe/utils/safe_exec.py
@@ -83,6 +83,7 @@ def get_safe_globals():
make_post_request = frappe.integrations.utils.make_post_request,
socketio_port=frappe.conf.socketio_port,
get_hooks=frappe.get_hooks,
+ sanitize_html=frappe.utils.sanitize_html
),
style=frappe._dict(
border_color='#d1d8dd'
diff --git a/frappe/www/search.py b/frappe/www/search.py
index 19df1e48e0..a8bb1a5294 100644
--- a/frappe/www/search.py
+++ b/frappe/www/search.py
@@ -13,7 +13,7 @@ def get_context(context):
context.title = _('Search Results for ')
context.query = query
context.route = '/search'
- context.update(get_search_results(query, frappe.form_dict.scope))
+ context.update(get_search_results(query, frappe.utils.sanitize_html(frappe.form_dict.scope)))
else:
context.title = _('Search')