Merge branch 'staging' into develop

This commit is contained in:
Saurabh 2018-12-13 10:37:25 +05:30
commit b727233948
30 changed files with 262 additions and 128 deletions

View file

@ -17,6 +17,10 @@ def clear_user_cache(user=None):
"defaults", "user_permissions", "home_page", "linked_with",
"desktop_icons", 'portal_menu_items')
# this will automatically reload the global cache
# so it is important to clear this first
clear_notifications(user)
if user:
for name in groups:
cache.hdel(name, user)
@ -28,8 +32,6 @@ def clear_user_cache(user=None):
clear_defaults_cache()
clear_global_cache()
clear_notifications(user)
def clear_global_cache():
from frappe.website.render import clear_cache as clear_website_cache

View file

@ -184,15 +184,17 @@ def list_apps(context):
@click.argument('email')
@click.option('--first-name')
@click.option('--last-name')
@click.option('--password')
@click.option('--send-welcome-email', default=False, is_flag=True)
@pass_context
def add_system_manager(context, email, first_name, last_name, send_welcome_email):
def add_system_manager(context, email, first_name, last_name, send_welcome_email, password):
"Add a new system manager to a site"
import frappe.utils.user
for site in context.sites:
frappe.connect(site=site)
try:
frappe.utils.user.add_system_manager(email, first_name, last_name, send_welcome_email)
frappe.utils.user.add_system_manager(email, first_name, last_name,
send_welcome_email, password)
frappe.db.commit()
finally:
frappe.destroy()

View file

@ -9,6 +9,8 @@ from frappe.commands import pass_context, get_site
from frappe.utils import update_progress_bar, get_bench_path
from frappe.utils.response import json_handler
from coverage import Coverage
import cProfile, pstats
from six import StringIO
@click.command('build')
@click.option('--app', help='Build assets for app')
@ -114,8 +116,9 @@ def reset_perms(context):
@click.argument('method')
@click.option('--args')
@click.option('--kwargs')
@click.option('--profile', is_flag=True, default=False)
@pass_context
def execute(context, method, args=None, kwargs=None):
def execute(context, method, args=None, kwargs=None, profile=False):
"Execute a function"
for site in context.sites:
try:
@ -135,8 +138,18 @@ def execute(context, method, args=None, kwargs=None):
else:
kwargs = {}
if profile:
pr = cProfile.Profile()
pr.enable()
ret = frappe.get_attr(method)(*args, **kwargs)
if profile:
pr.disable()
s = StringIO()
pstats.Stats(pr, stream=s).sort_stats('cumulative').print_stats(.5)
print(s.getvalue())
if frappe.db:
frappe.db.commit()
finally:

View file

@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils.jinja import validate_template
from frappe import _
@ -17,7 +18,8 @@ class AddressTemplate(Document):
if not self.is_default:
if not self.defaults:
self.is_default = 1
frappe.msgprint(_("Setting this Address Template as default as there is no other default"))
if cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
frappe.msgprint(_("Setting this Address Template as default as there is no other default"))
validate_template(self.template)

View file

@ -106,7 +106,9 @@ def create_custom_field(doctype, df, ignore_validate=False):
"dt": doctype,
"permlevel": 0,
"fieldtype": 'Data',
"hidden": 0
"hidden": 0,
# Looks like we always use this programatically?
# "is_standard": 1
})
custom_field.update(df)
custom_field.flags.ignore_validate = ignore_validate
@ -125,6 +127,7 @@ def create_custom_fields(custom_fields, ignore_validate = False, update=True):
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]})
if not field:
try:
df["owner"] = "Administrator"
create_custom_field(doctype, df, ignore_validate=ignore_validate)
except frappe.exceptions.DuplicateEntryError:
pass

View file

@ -137,7 +137,8 @@ def quick_kanban_board(doctype, board_name, field_name, project=None):
'label': 'Kanban Column',
'fieldname': 'kanban_column',
'fieldtype': 'Select',
'hidden': 1
'hidden': 1,
'owner': 'Administrator'
})
meta = frappe.get_meta(doctype)

View file

@ -29,7 +29,7 @@ def get_notifications():
notification_count[name] = count
return {
"open_count_doctype": get_notifications_for_doctypes(config, notification_count),
"open_count_doctype": {},
"open_count_module": get_notifications_for_modules(config, notification_count),
"open_count_other": get_notifications_for_other(config, notification_count),
"targets": get_notifications_for_targets(config, notification_percent),

View file

@ -356,3 +356,55 @@ def enable_twofactor_all_roles():
all_role.two_factor_auth = True
all_role.save(ignore_permissions=True)
def make_records(records, debug=False):
from frappe import _dict
from frappe.modules import scrub
if debug:
print("make_records: in DEBUG mode")
# LOG every success and failure
for record in records:
doctype = record.get("doctype")
condition = record.get('__condition')
if condition and not condition():
continue
doc = frappe.new_doc(doctype)
doc.update(record)
# ignore mandatory for root
parent_link_field = ("parent_" + scrub(doc.doctype))
if doc.meta.get_field(parent_link_field) and not doc.get(parent_link_field):
doc.flags.ignore_mandatory = True
try:
doc.insert(ignore_permissions=True)
except frappe.DuplicateEntryError as e:
# print("Failed to insert duplicate {0} {1}".format(doctype, doc.name))
# pass DuplicateEntryError and continue
if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
# make sure DuplicateEntryError is for the exact same doc and not a related doc
pass
else:
raise
except Exception as e:
exception = record.get('__exception')
if exception:
config = _dict(exception)
if isinstance(e, config.exception):
config.handler()
else:
show_document_insert_error()
else:
show_document_insert_error()
def show_document_insert_error():
print("Document Insert Error")
print(frappe.get_traceback())

View file

@ -12,7 +12,7 @@ source_link = "https://github.com/frappe/frappe"
app_license = "MIT"
develop_version = '12.x.x-develop'
staging_version = '11.0.3-beta.36'
staging_version = '11.0.3-beta.37'
app_email = "info@frappe.io"

View file

@ -80,6 +80,11 @@ frappe.Application = Class.extend({
this.show_update_available();
if(frappe.ui.startup_setup_dialog && !frappe.boot.setup_complete) {
frappe.ui.startup_setup_dialog.pre_show();
frappe.ui.startup_setup_dialog.show();
}
// listen to csrf_update
frappe.realtime.on("csrf_generated", function(data) {
// handles the case when a user logs in again from another tab

View file

@ -124,4 +124,8 @@ frappe.ui.form.ControlComment = frappe.ui.form.ControlTextEditor.extend({
['clean']
];
},
});
clear() {
this.quill.setText('');
}
});

View file

@ -125,8 +125,11 @@ frappe.views.ListSidebar = class ListSidebar {
add_reports(this.list_view.settings.reports);
}
// Sort reports alphabetically
var reports = Object.values(frappe.boot.user.all_reports).sort((a,b) => a.title.localeCompare(b.title)) || [];
// from specially tagged reports
add_reports(frappe.boot.user.all_reports || []);
add_reports(reports);
}
setup_list_filter() {

View file

@ -92,6 +92,15 @@ Object.assign(frappe.utils, {
return txt.toLowerCase().substr(0,7)=='http://'
|| txt.toLowerCase().substr(0,8)=='https://'
},
to_title_case: function(string, with_space=false) {
let titlecased_string = string.toLowerCase().replace(/(?:^|[\s-/])\w/g, function(match) {
return match.toUpperCase();
});
let replace_with = with_space ? ' ' : '';
return titlecased_string.replace(/-|_/g, replace_with);
},
toggle_blockquote: function(txt) {
if (!txt) return txt;

View file

@ -44,19 +44,24 @@ frappe.route = function() {
frappe.route_history.push(route);
if(route[0] && route[1] && frappe.views[route[0] + "Factory"]) {
// has a view generator, generate!
if(!frappe.view_factory[route[0]]) {
frappe.view_factory[route[0]] = new frappe.views[route[0] + "Factory"]();
}
if(route[0]) {
const title_cased_route = frappe.utils.to_title_case(route[0]);
frappe.view_factory[route[0]].show();
} else {
// show page
const route_name = frappe.utils.xss_sanitise(route[0]);
if (frappe.views.pageview) {
if(route[1] && frappe.views[title_cased_route + "Factory"]) {
// has a view generator, generate!
if(!frappe.view_factory[title_cased_route]) {
frappe.view_factory[title_cased_route] = new frappe.views[title_cased_route + "Factory"]();
}
frappe.view_factory[title_cased_route].show();
} else {
// show page
const route_name = frappe.utils.xss_sanitise(route[0]);
frappe.views.pageview.show(route_name);
}
} else {
// Show desk
frappe.views.pageview.show('');
}

View file

@ -20,6 +20,14 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup {
make() {
this.$wrapper = frappe.get_modal("", "");
if(this.static) {
this.$wrapper.modal({
backdrop: 'static',
keyboard: false
});
this.get_close_btn().hide();
}
this.wrapper = this.$wrapper.find('.modal-dialog')
.get(0);
if ( this.size == "small" )

View file

@ -17,7 +17,7 @@
<button class="btn btn-default btn-xs btn-order"
data-value="{{ sort_order }}">
<span class="octicon text-muted
octicon-arrow-{{ sort_order===" desc " ? "down " : "up " }}">
octicon-arrow-{{ sort_order==="desc" ? "down " : "up " }}">
</span>
</button>
</div>

View file

@ -11,13 +11,6 @@
<div class="navbar-center ellipsis" style="display: none;"></div>
<ul class="nav navbar-nav navbar-right">
<li class="user-progress hide" title="Your Setup Progress">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" onclick="return false;" style="height: 40px;">
<div class="progress-chart" style="width: 50px; margin-top: 8px;"><div class="progress">
<div class="progress-bar"></div>
</div></div>
</a>
</li>
<li class="visible-xs">
<a class="navbar-search-button" href="#" data-toggle="modal" data-target="#search-modal"><i class="octicon octicon-search"></i></a>
</li>

View file

@ -22,8 +22,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
this.setup_sidebar();
this.setup_help();
this.setup_modules_dialog();
this.setup_progress_dialog();
this.bind_events();
$(document).trigger('toolbar_setup');
@ -199,41 +197,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
});
}
}
},
setup_progress_dialog: function() {
var me = this;
frappe.call({
method: "frappe.desk.user_progress.get_user_progress_slides",
type: 'GET',
callback: function(r) {
if(r.message) {
let slides = r.message;
if(slides.length && slides.map(s => parseInt(s.done)).includes(0)) {
frappe.require("assets/frappe/js/frappe/ui/toolbar/user_progress_dialog.js", function() {
me.progress_dialog = new frappe.setup.UserProgressDialog({
slides: slides
});
$('.user-progress').removeClass('hide');
$('.user-progress .dropdown-toggle').on('click', () => {
me.progress_dialog.show();
});
if (cint(frappe.boot.sysdefaults.is_first_startup)) {
me.progress_dialog.show();
frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.reset_is_first_startup",
args: {},
callback: () => {}
});
}
});
}
}
},
freeze: false
});
}
});
@ -260,6 +223,18 @@ $.extend(frappe.ui.toolbar, {
frappe.ui.toolbar.get_menu(menu) : menu;
$('<li class="divider custom-menu"></li>').prependTo(menu);
},
add_icon_link(route, icon, index, class_name) {
let parent_element = $(".navbar-right").get(0);
let new_element = $(`<li class="${class_name}">
<a class="btn" href="${route}" title="${frappe.utils.to_title_case(class_name, true)}" aria-haspopup="true" aria-expanded="true">
<div>
<i class="octicon ${icon}"></i>
</div>
</a>
</li>`).get(0);
parent_element.insertBefore(new_element, parent_element.children[index]);
}
});

View file

@ -952,12 +952,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
show_tip() {
const part1 = __('For comparative filters, start with ">" or "<" or "=", e.g. >5 or =324');
const part2 = __('For ranges use ":", e.g. "5:10" (to filter values between 5 & 10');
this.page.footer.removeClass('hide').addClass('text-muted text-center').html(`
<p>${part1}</p>
<p>${part2}</p>
`);
const message = __('For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10).');
this.page.footer.removeClass('hide').addClass('text-muted text-center').html(`<p>${message}</p>`);
}
message_div(message) {

View file

@ -32,6 +32,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.order_by = this.report_doc.json.order_by;
this.add_totals_row = this.report_doc.json.add_totals_row;
this.page_title = this.report_name;
this.page_length = this.report_doc.json.page_length || 20;
this.order_by = this.report_doc.json.order_by || 'modified desc';
});
}
}
@ -39,7 +41,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
setup_view() {
this.setup_columns();
this.bind_charts_button();
this.setup_dynamic_row_height_check();
}
setup_result_area() {
@ -50,8 +51,53 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.$result.append(this.$datatable_wrapper);
}
setup_paging_area() {
super.setup_paging_area();
const message = __('For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10).');
this.$paging_area.find('.level-left').append(
`<p class="text-muted text-medium margin-left">${message}</p>`
)
}
setup_sort_selector() {
this.sort_selector = new frappe.ui.SortSelector({
parent: this.filter_area.$filter_list_wrapper,
doctype: this.doctype,
args: this.order_by,
onchange: this.on_sort_change.bind(this)
});
}
before_render() {
this.save_report_settings();
if (this.report_doc) {
this.set_dirty_state_for_custom_report();
} else {
this.save_report_settings();
}
}
set_dirty_state_for_custom_report() {
const json = JSON.stringify({
filters: this.filter_area.get(),
fields: this.fields,
order_by: this.sort_selector.get_sql_string(),
add_totals_row: this.add_totals_row,
page_length: this.page_length
});
const report_json = JSON.stringify({
filters: this.report_doc.json.filters,
fields: this.report_doc.json.fields,
order_by: this.report_doc.json.order_by,
add_totals_row: this.report_doc.json.add_totals_row,
page_length: this.report_doc.json.page_length
});
if (json != report_json) {
this.page.set_indicator(__('Not Saved'), 'orange');
} else {
this.page.clear_indicator();
}
}
save_report_settings() {
@ -121,16 +167,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
}
on_filter_change() {
if (this.report_doc) {
if (JSON.stringify(this.filters) !== JSON.stringify(this.filter_area.get())) {
this.page.set_indicator(__('Not Saved'), 'orange');
} else {
this.page.clear_indicator();
}
}
}
update_row(doc, flash_row) {
const to_refresh = [];
@ -178,8 +214,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
columns: this.columns,
data: this.get_data(values),
getEditor: this.get_editing_object.bind(this),
dynamicRowHeight: !this.fixed_row_height.get_value(),
checkboxColumn: true,
inlineFilters: true,
cellHeight: 37,
events: {
onRemoveColumn: (column) => {
@ -534,7 +570,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
set_fields() {
if (this.report_name && this.report_doc.json.fields) {
this.fields = this.report_doc.json.fields;
this.fields = this.report_doc.json.fields.slice();
return;
} else if (this.view_user_settings.fields) {
// get from user_settings
@ -857,23 +893,6 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
});
}
setup_dynamic_row_height_check() {
this.fixed_row_height = frappe.ui.form.make_control({
df: {
fieldtype: 'Check',
fieldname: 'fixed_row_height',
label: __('Fixed height'),
onchange: () => {
this.render(true);
}
},
parent: this.$paging_area.find('.level-left'),
render_input: true
});
this.fixed_row_height.$wrapper.addClass('report-action-checkbox');
this.fixed_row_height.set_value(1);
}
get_checked_items(only_docnames) {
const indexes = this.datatable.rowmanager.getCheckedRows();
const items = indexes.filter(i => i != undefined)
@ -889,17 +908,20 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
save_report(save_type) {
const _save_report = (name) => {
// callback
const report_settings = {
filters: this.filter_area.get(),
fields: this.fields,
order_by: this.sort_selector.get_sql_string(),
add_totals_row: this.add_totals_row,
page_length: this.page_length
}
return frappe.call({
method: 'frappe.desk.reportview.save_report',
args: {
name: name,
doctype: this.doctype,
json: JSON.stringify({
filters: this.filter_area.get(),
fields: this.fields,
order_by: this.sort_selector.get_sql_string(),
add_totals_row: this.add_totals_row
})
json: JSON.stringify(report_settings)
},
callback:(r) => {
if(r.exc) {
@ -907,12 +929,20 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
return;
}
if(r.message != this.report_name) {
// Rerender the reports dropdown,
// so that this report is included in the dropdown as well.
frappe.boot.user.all_reports[r.message] = {
ref_doctype: "Item",
report_type: "Report Builder",
title: r.message,
};
this.list_sidebar.setup_reports();
frappe.set_route('List', this.doctype, 'Report', r.message);
}
// reset dirty state
this.filters = this.filter_area.get();
this.on_filter_change();
// update state
this.report_doc.json = report_settings;
this.set_dirty_state_for_custom_report();
}
});

View file

@ -116,7 +116,7 @@ kbd {
min-width: 200px;
padding: 0px;
font-size: @text-medium;
max-height: 400px;
max-height: 500px;
overflow: auto;
// only rounded bottoms
border-radius: 0px 0px 4px 4px;

View file

@ -31,6 +31,12 @@
justify-content: flex-start;
}
.flush-top {
display: flex;
justify-content: space-between;
align-items: end;
}
.level {
display: flex;
justify-content: space-between;

View file

@ -3,11 +3,26 @@
@import "avatar.less";
@import "indicator.less";
html, body {
height: 100%;
margin: 0; padding: 0; /* to avoid scrollbars */
}
body {
font-family: @font-stack;
color: @text-color;
}
.main-section {
display: flex;
min-height: 100%;
flex-direction: column;
}
.wrapper {
flex: 1;
}
a& {
color: @text-color;

View file

@ -158,6 +158,8 @@ def get():
bootinfo["lang"] = frappe.translate.get_user_lang()
bootinfo["disable_async"] = frappe.conf.disable_async
bootinfo["setup_complete"] = cint(frappe.db.get_single_value('System Settings', 'setup_complete'))
# limits
bootinfo.limits = get_limits()
bootinfo.expiry_message = get_expiry_message()

View file

@ -37,7 +37,7 @@
{% block body %}
<body data-path="{{ path }}">
<div class="main-section">
<div>
<div class="wrapper">
<header>
{%- block banner -%}
{% include "templates/includes/banner_extension.html" ignore missing %}

View file

@ -3,14 +3,14 @@ from __future__ import unicode_literals
from unittest import TestCase
from dateutil.relativedelta import relativedelta
from frappe.utils.scheduler import (enqueue_applicable_events, restrict_scheduler_events_if_dormant,
get_enabled_scheduler_events, disable_scheduler_on_expiry)
get_enabled_scheduler_events, disable_scheduler_on_expiry)
from frappe import _dict
from frappe.utils.background_jobs import enqueue
from frappe.utils import now_datetime, today, add_days, add_to_date
from frappe.limits import update_limits, clear_limit
import frappe
import json, time
import time
def test_timeout():
'''This function needs to be pickleable'''
@ -49,12 +49,12 @@ class TestScheduler(TestCase):
frappe.flags.enabled_events = None
def test_enabled_events_day_change(self):
val = json.dumps(["daily", "daily_long", "weekly", "weekly_long",
"monthly", "monthly_long"])
frappe.db.set_global('enabled_scheduler_events', val)
# TEMP for debug: this test fails randomly
print('Setting enabled_scheduler_events {0}'.format(val))
# use flags instead of globals as this test fails intermittently
# the root cause has not been identified but the culprit seems cache
# since cache is mutable, it maybe be changed by a parallel process
frappe.flags.enabled_events = ["daily", "daily_long", "weekly", "weekly_long",
"monthly", "monthly_long"]
# maintain last_event and next_event on different days
next_event = now_datetime().replace(hour=0, minute=0, second=0, microsecond=0)
@ -65,6 +65,8 @@ class TestScheduler(TestCase):
self.assertTrue("all" in frappe.flags.ran_schedulers)
self.assertFalse("hourly" in frappe.flags.ran_schedulers)
frappe.flags.enabled_events = None
def test_restrict_scheduler_events(self):
frappe.set_user("Administrator")

View file

@ -36,8 +36,8 @@ def after_install():
# update admin password
update_password("Administrator", get_admin_password())
# setup wizard now in frappe
frappe.db.set_default('desktop:home_page', 'setup-wizard')
if not frappe.conf.skip_setup_wizard:
frappe.db.set_default('desktop:home_page', 'setup-wizard')
# clear test log
with open(frappe.get_site_path('.test_log'), 'w') as f:
@ -157,4 +157,4 @@ def add_country_and_currency(name, country):
"smallest_currency_fraction_value": country.smallest_currency_fraction_value,
"number_format": country.number_format,
"docstatus": 0
}).db_insert()
}).db_insert()

View file

@ -263,7 +263,7 @@ def get_system_managers(only_name=False):
def add_role(user, role):
frappe.get_doc("User", user).add_roles(role)
def add_system_manager(email, first_name=None, last_name=None, send_welcome_email=False):
def add_system_manager(email, first_name=None, last_name=None, send_welcome_email=False, password=None):
# add user
user = frappe.new_doc("User")
user.update({
@ -276,6 +276,11 @@ def add_system_manager(email, first_name=None, last_name=None, send_welcome_emai
"send_welcome_email": 1 if send_welcome_email else 0
})
if password:
user.update({
"new_password": password
})
user.insert()
# add roles

View file

@ -35,6 +35,7 @@ class Workflow(Document):
"no_copy": 1,
"fieldtype": "Link",
"options": "Workflow State",
"owner": "Administrator"
}).save()
frappe.msgprint(_("Created Custom Field {0} in {1}").format(self.workflow_state_field,
@ -86,4 +87,4 @@ class Workflow(Document):
@frappe.whitelist()
def get_fieldnames_for(doctype):
return [f.fieldname for f in frappe.get_meta(doctype).fields \
if f.fieldname not in no_value_fields]
if f.fieldname not in no_value_fields]

View file

@ -6,7 +6,7 @@
<div class="page-card">
<div class='page-card-head'>
<span class='indicator blue'>{{ _("Reset Password") }}</span>
<span class='indicator blue'>{{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}</span>
</div>
<form id="reset-password">
<div class="form-group">