Merge branch 'develop' into fix-reviews-section

This commit is contained in:
Suraj Shetty 2020-05-08 14:13:18 +05:30 committed by GitHub
commit e5434d471c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 203 additions and 153 deletions

View file

@ -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,29 +27,24 @@ 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
- bench --site test_site_producer execute frappe.utils.install.complete_setup_wizard
script: bench --site test_site run-ui-tests frappe --headless
- name: "Python 2.7 MariaDB"
python: 2.7
env: DB=mariadb TYPE=server
script: bench --site test_site run-tests --coverage
before_install:
# install wkhtmltopdf
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz

View file

@ -3,16 +3,17 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
* @frappe/frappe-review-team
website/ @scmmishra
web_form/ @scmmishra
templates/ @scmmishra
www/ @scmmishra
integrations/ @Mangesh-Khairnar
patches/ @sahil28297
dashboard/ @prssanna
email/ @Thunderbottom
event_streaming/ @ruchamahabal
data_import* @netchampfaris
core/ @surajshetty3416
* @frappe/frappe-review-team
website/ @scmmishra
web_form/ @scmmishra
templates/ @scmmishra
www/ @scmmishra
integrations/ @Mangesh-Khairnar
patches/ @sahil28297
dashboard/ @prssanna
email/ @Thunderbottom
event_streaming/ @ruchamahabal
data_import* @netchampfaris
core/ @surajshetty3416
requirements.txt @gavindsouza
commands/ @gavindsouza

View file

@ -49,7 +49,11 @@ class _dict(dict):
return _dict(dict(self).copy())
def _(msg, lang=None, context=None):
"""Returns translated string in current lang, if exists."""
"""Returns translated string in current lang, if exists.
Usage:
_('Change')
_('Change', context='Coins')
"""
from frappe.translate import get_full_dict
from frappe.utils import strip_html_tags, is_html

View file

@ -81,7 +81,7 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
installing = touch_file(get_site_path('locks', 'installing.lock'))
atexit.register(_new_site_cleanup, site, mariadb_root_username, mariadb_root_password)
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
admin_password=admin_password, verbose=verbose, source_sql=source_sql, force=force, reinstall=reinstall,
db_password=db_password, db_type=db_type, db_host=db_host, db_port=db_port, no_mariadb_socket=no_mariadb_socket)
apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
@ -101,7 +101,7 @@ def _new_site_cleanup(site, mariadb_root_username, mariadb_root_password):
if installing and os.path.exists(installing):
if mariadb_root_password:
_drop_site(site, mariadb_root_username, mariadb_root_password, force=True)
_drop_site(site, mariadb_root_username, mariadb_root_password, force=True, no_backup=True)
shutil.rmtree(site)
frappe.destroy()

View file

@ -102,7 +102,7 @@ class User(Document):
'frappe.core.doctype.user.user.create_contact',
user=self,
ignore_mandatory=True,
now=frappe.flags.in_test
now=frappe.flags.in_test or frappe.flags.in_install
)
if self.name not in ('Administrator', 'Guest') and not self.user_image:
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name)

View file

@ -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
@ -27,7 +27,7 @@ def get_permission_query_conditions(user):
return None
allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read())
allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()])
allowed_reports = tuple([key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()])
return '''
`tabDashboard Chart`.`document_type` in {allowed_doctypes}

View file

@ -193,30 +193,12 @@ class Leaderboard {
this.$search_box =
$(`<div class="leaderboard-search form-group col-md-3">
<input type="text" placeholder="Search" class="form-control leaderboard-search-input input-sm">
<input type="text" placeholder="Search" data-element="search" class="form-control leaderboard-search-input input-sm">
</div>`);
$(this.parent).find(".page-form").append(this.$search_box);
}
setup_search(list_items) {
let $search_input = this.$search_box.find(".leaderboard-search-input");
this.$search_box.on("keyup", ()=> {
let text_filter = $search_input.val().toLowerCase();
text_filter = text_filter.replace(/^\s+|\s+$/g, '');
for (var i = 0; i < list_items.length; i++) {
let text = list_items.eq(i).find(".list-id").text().trim().toLowerCase();
if (text.includes(text_filter)) {
list_items.eq(i).css("display", "");
} else {
list_items.eq(i).css("display", "none");
}
}
});
}
show_leaderboard(doctype) {
if (this.doctypes.length) {
if (this.doctypes.includes(doctype)) {
@ -278,7 +260,7 @@ class Leaderboard {
if (res && res.message.length) {
me.message = null;
me.$container.find(".leaderboard-list").html(me.render_list_view(res.message));
me.setup_search($(me.parent).find('.list-item-container'));
frappe.utils.setup_search($(me.parent), ".list-item-container", ".list-id");
} else {
me.$graph_area.hide();
me.message = __("No items found.");

View file

@ -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)

View file

@ -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='<h1>Whatever</h1>',
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

View file

@ -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()

View file

@ -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"),

View file

@ -277,3 +277,4 @@ frappe.patches.v13_0.set_existing_dashboard_charts_as_public
frappe.patches.v13_0.set_path_for_homepage_in_web_page_view
frappe.patches.v13_0.migrate_translation_column_data
frappe.patches.v13_0.set_read_times
frappe.patches.v13_0.remove_web_view

View file

@ -2,14 +2,4 @@ import frappe
def execute():
frappe.reload_doctype('Translation')
frappe.db.sql("""
UPDATE `tabTranslation`
SET
translated_text=target_name,
source_text=source_name,
contribution_status=(CASE status
WHEN 'Deleted' THEN 'Rejected'
ELSE ''
END),
contributed=0
""")
frappe.db.sql("UPDATE `tabTranslation` SET `translated_text`=`target_name`, `source_text`=`source_name`, `contributed`=0")

View file

@ -14,5 +14,5 @@ def get_read_time(blog):
if blog.content_type == "Markdown":
content = markdown(blog.content_md)
total_words = len(strip_html_tags(content).split())
total_words = len(strip_html_tags(content or "").split())
return ceil(total_words/250)

View file

@ -54,6 +54,22 @@ Quill.register(FontStyle, true);
Quill.register(AlignStyle, true);
Quill.register(DirectionStyle, true);
// replace font tag with span
const Inline = Quill.import('blots/inline');
class CustomColor extends Inline {
constructor(domNode, value) {
super(domNode, value);
this.domNode.style.color = this.domNode.color;
domNode.outerHTML = this.domNode.outerHTML.replace(/<font/g, '<span').replace(/<\/font>/g, '</span>');
}
}
CustomColor.blotName = "customColor";
CustomColor.tagName = "font";
Quill.register(CustomColor, true);
frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
make_wrapper() {
this._super();

View file

@ -71,7 +71,7 @@
</a>
<ul class="dropdown-menu list-stats-dropdown" role="menu">
<div class="dropdown-search">
<input type="text" placeholder="Search" class="form-control dropdown-search-input input-xs">
<input type="text" placeholder="Search" data-element="search" class="form-control input-xs">
</div>
</ul>
</div>

View file

@ -242,40 +242,6 @@ frappe.views.ListSidebar = class ListSidebar {
});
}
setup_dropdown_search(dropdown, text_class) {
let $dropdown_search = dropdown.find('.dropdown-search').show();
let $search_input = $dropdown_search.find('.dropdown-search-input');
$search_input.focus();
$dropdown_search.on('click',(e)=>{
e.stopPropagation();
});
let $elements = dropdown.find('li');
$dropdown_search.on('keyup',()=> {
let text_filter = $search_input.val().toLowerCase();
// Replace trailing and leading spaces
text_filter = text_filter.replace(/^\s+|\s+$/g, '');
for (var i = 0; i < $elements.length; i++) {
let text_element = $elements.eq(i).find(text_class);
let text = text_element.text().toLowerCase();
// Search data-name since label for current user is 'Me'
let name = '';
if (text_element.data('name')) {
name = text_element.data('name').toLowerCase();
}
if (text.includes(text_filter) || name.includes(text_filter)) {
$elements.eq(i).css('display','');
} else {
$elements.eq(i).css('display','none');
}
}
});
dropdown.parent().on('hide.bs.dropdown',()=> {
$dropdown_search.val('');
});
}
get_cat_tags() {
return this.cat_tags;
}
@ -294,7 +260,7 @@ frappe.views.ListSidebar = class ListSidebar {
callback: function(r) {
me.render_stat("_user_tags", (r.message.stats || {})["_user_tags"]);
let stats_dropdown = me.sidebar.find('.list-stats-dropdown');
me.setup_dropdown_search(stats_dropdown,'.stat-label');
frappe.utils.setup_search(stats_dropdown, '.stat-link', '.stat-label');
}
});
}

View file

@ -22,6 +22,7 @@ frappe.views.ListGroupBy = class ListGroupBy {
title: __("Select Filters"),
fields: this.get_group_by_dropdown_fields()
});
d.set_primary_action("Save", ({ group_by_fields }) => {
frappe.model.user_settings.save(this.doctype, 'group_by_fields', group_by_fields || null);
this.group_by_fields = group_by_fields ? ['assigned_to', 'owner', ...group_by_fields] : ['assigned_to', 'owner'];
@ -29,7 +30,12 @@ frappe.views.ListGroupBy = class ListGroupBy {
d.hide();
});
this.page.sidebar.find(".add-list-group-by a ").on("click", () => {
d.$body.prepend(`<div class="filters-search">
<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
</div>`);
this.page.sidebar.find(".add-list-group-by a").on("click", () => {
frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
d.show();
});
}
@ -95,7 +101,7 @@ frappe.views.ListGroupBy = class ListGroupBy {
this.get_group_by_count(fieldname).then(field_count_list => {
if (field_count_list.length) {
this.render_dropdown_items(field_count_list, fieldtype, dropdown);
this.sidebar.setup_dropdown_search(dropdown, '.group-by-value');
frappe.utils.setup_search(dropdown, '.group-by-item', '.group-by-value', 'data-name');
} else {
dropdown.find('.group-by-loading').html(`${__("No filters found")}`);
}
@ -165,7 +171,7 @@ frappe.views.ListGroupBy = class ListGroupBy {
};
let standard_html = `
<div class="dropdown-search">
<input type="text" placeholder="${__('Search')}" class="form-control dropdown-search-input input-xs">
<input type="text" placeholder="${__('Search')}" data-element="search" class="dropdown-search-input form-control input-xs">
</div>
`;

View file

@ -695,7 +695,32 @@ Object.assign(frappe.utils, {
return null;
}
},
setup_search($wrapper, el_class, text_class, data_attr) {
const $search_input = $wrapper.find('[data-element="search"]').show();
$search_input.focus().val('');
const $elements = $wrapper.find(el_class).show();
$search_input.off('keyup').on('keyup', () => {
let text_filter = $search_input.val().toLowerCase();
// Replace trailing and leading spaces
text_filter = text_filter.replace(/^\s+|\s+$/g, '');
for (let i = 0; i < $elements.length; i++) {
const text_element = $elements.eq(i).find(text_class);
const text = text_element.text().toLowerCase();
let name = '';
if (data_attr && text_element.attr(data_attr)) {
name = text_element.attr(data_attr).toLowerCase();
}
if (text.includes(text_filter) || name.includes(text_filter)) {
$elements.eq(i).css('display', '');
} else {
$elements.eq(i).css('display', 'none');
}
}
});
},
deep_equal(a, b) {
return deep_equal(a, b);
},

View file

@ -1286,6 +1286,11 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
});
d.$body.prepend(`<div class="columns-search">
<input type="text" placeholder="${__('Search')}" data-element="search" class="form-control input-xs">
</div>`);
frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area');
d.show();
}
}

View file

@ -480,7 +480,7 @@ export default class ChartWidget extends Widget {
colors.push(field.color);
});
} else if (["Line", "Bar"].includes(this.chart_doc.type)) {
colors = [this.chart_doc.color || "light-blue"];
colors = [this.chart_doc.color || []];
}
if (!this.data || !this.data.labels.length || !Object.keys(this.data).length) {

View file

@ -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 {
</div>
`);
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() {

View file

@ -68,6 +68,10 @@
}
}
.columns-search {
margin-bottom: 10px;
}
.report-wrapper {
overflow: auto;
}

View file

@ -413,6 +413,9 @@ body[data-route^="Module"] .main-menu {
}
}
.filters-search {
margin-bottom: 10px;
}
// module sidebar
.layout-side-section .module-sidebar-nav {
margin-top: 15px;

View file

@ -28,7 +28,7 @@
</div>
</div>
{% if frappe.form_dict.scope %}
<input type="text" hidden name="scope" value="{{ frappe.form_dict.scope }}">
<input type="text" hidden name="scope" value="{{ frappe.sanitize_html(frappe.form_dict.scope) }}">
{% endif %}
</form>
</div>

View file

@ -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'

View file

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import frappe, unittest, os
import frappe.translate
from frappe import _
dirname = os.path.dirname(__file__)
translation_string_file = os.path.join(dirname, 'translation_test_file.txt')
@ -13,6 +14,11 @@ class TestTranslate(unittest.TestCase):
data = frappe.translate.get_messages_from_file(translation_string_file)
self.assertListEqual(data, expected_output)
def test_translation_with_context(self):
frappe.local.lang = 'fr'
self.assertEqual(_('Change'), 'Changement')
self.assertEqual(_('Change', context='Coins'), 'la monnaie')
expected_output = [
('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', 'This is some context', 2),
('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', None, 4),

View file

@ -232,7 +232,7 @@ def get_translation_dict_from_file(path, lang, app):
for item in csv_content:
if len(item)==3 and item[2]:
key = item[1] + ':' + item[2]
key = item[0] + ':' + item[2]
translation_map[key] = strip(item[1])
elif len(item) in [2, 3]:
translation_map[item[0]] = strip(item[1])

View file

@ -4141,3 +4141,5 @@ Path,Chemin,
Components,Composants,
Verified By,Vérifié Par,
Lead Conversion Time,Temps de conversion des prospects,
Change,Changement,
Change,la monnaie,Coins
1 A4 A4
4141 Components Composants
4142 Verified By Vérifié Par
4143 Lead Conversion Time Temps de conversion des prospects
4144 Change Changement
4145 Change la monnaie Coins

View file

@ -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

View file

@ -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'

View file

@ -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')