Merge branch 'develop' of https://github.com/frappe/frappe into energy_point_new

This commit is contained in:
Suraj Shetty 2019-03-28 19:54:36 +05:30
commit b968c207d1
73 changed files with 8089 additions and 6473 deletions

View file

@ -137,6 +137,7 @@
"Cypress": true,
"cy": true,
"it": true,
"expect": true,
"context": true,
"before": true,
"beforeEach": true

View file

@ -0,0 +1,38 @@
context('Rating Control', () => {
beforeEach(() => {
cy.login('Administrator', 'qwe');
});
it('click on the star rating to record value', () => {
cy.visit('/desk');
cy.dialog('Rating', [{
'fieldname': 'rate',
'fieldtype': 'Rating',
}]).as('dialog');
cy.get('div.rating')
.children('i.fa')
.first()
.click()
.should('have.class', 'star-click');
cy.get('@dialog').then(dialog => {
var value = dialog.get_value('rate');
expect(value).to.equal(1);
});
});
it('hover on the star', () => {
cy.visit('/desk');
cy.dialog('Rating', [{
'fieldname': 'rate',
'fieldtype': 'Rating',
}]);
cy.get('div.rating')
.children('i.fa')
.first()
.invoke('trigger', 'mouseenter')
.should('have.class', 'star-hover')
.invoke('trigger', 'mouseleave')
.should('not.have.class', 'star-hover');
});
});

View file

@ -61,3 +61,17 @@ Cypress.Commands.add('new_form', (doctype) => {
Cypress.Commands.add('go_to_list', (doctype) => {
cy.visit(`/desk#List/${doctype}/List`);
});
Cypress.Commands.add('dialog', (title, fields) => {
cy.window().then(win => {
var d = new win.frappe.ui.Dialog({
title: title,
fields: fields,
primary_action: function(){
d.hide();
}
});
d.show();
return d;
});
});

View file

@ -24,7 +24,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
__version__ = '11.1.16'
__version__ = '11.1.17'
__title__ = "Frappe Framework"
local = Local()

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

View file

@ -0,0 +1,27 @@
.chart-container {
border: 1px solid #d1d8dd;
border-radius: 4px;
margin: 15px 0;
padding: 15px;
}
.chart-actions {
position: absolute;
right: 30px;
top: 54px;
}
.chart-column-container {
position: relative;
}
.last-synced-text {
position: absolute;
top: 56px;
right: 60px;
font-size: 12px;
}
.dashboard-graph {
padding-top: 15px;
}

View file

@ -0,0 +1,234 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.pages['dashboard'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __("Dashboard"),
single_column: true
});
frappe.dashboard = new Dashboard(wrapper);
$(wrapper).bind('show', function() {
frappe.dashboard.show();
});
};
class Dashboard {
constructor(wrapper) {
this.wrapper = $(wrapper);
$(`<div class="dashboard">
<div class="dashboard-graph row"></div>
</div>`).appendTo(this.wrapper.find(".page-content").empty());
this.container = this.wrapper.find(".dashboard-graph");
this.page = wrapper.page;
}
show() {
this.route = frappe.get_route();
const current_dashboard_name = this.route.slice(-1)[0];
if(this.dashboard_name !== current_dashboard_name) {
this.dashboard_name = current_dashboard_name;
this.page.set_title(this.dashboard_name);
this.set_dropdown();
this.container.empty();
this.refresh();
}
this.charts = {};
}
refresh() {
this.get_dashboard_doc().then((doc) => {
this.dashboard_doc = doc;
this.charts = this.dashboard_doc.charts;
this.charts.map((chart) => {
let chart_container = $("<div></div>");
chart_container.appendTo(this.container);
frappe.model.with_doc("Dashboard Chart", chart.chart).then( chart_doc => {
let dashboard_chart = new DashboardChart(chart_doc, chart_container);
dashboard_chart.show();
});
});
});
}
get_dashboard_doc() {
return frappe.model.with_doc('Dashboard', this.dashboard_name);
}
set_dropdown() {
this.page.clear_menu();
frappe.db.get_list("Dashboard").then(dashboards => {
dashboards.map(dashboard => {
let name = dashboard.name;
if(name != this.dashboard_name){
this.page.add_menu_item(name, () => frappe.set_route("dashboard", name));
}
});
});
}
}
class DashboardChart {
constructor(chart_doc, chart_container) {
this.chart_doc = chart_doc;
this.container = chart_container;
}
show() {
this.get_settings().then(() => {
this.prepare_chart_object();
this.prepare_container();
this.prepare_chart_actions();
this.fetch(this.filters).then((data) => {
this.update_last_synced();
this.data = data;
this.render();
});
});
}
prepare_container() {
const column_width_map = {
"Half": "6",
"Full": "12",
};
let columns = column_width_map[this.chart_doc.width];
this.chart_container = $(`<div class="col-sm-${columns} chart-column-container">
<div class="chart-wrapper"></div>
</div>`);
this.chart_container.appendTo(this.container);
let last_synced_text = $(`<span class="text-muted last-synced-text"></span>`);
last_synced_text.prependTo(this.chart_container);
}
prepare_chart_actions() {
let actions = [
{
label: __("Set Filters"),
action: "set-filters",
handler: this.create_set_filters_dialog.bind(this)
},
{
label: __("Force Refresh"),
action: "force-refresh",
handler: () => {
this.fetch(this.filters, true).then(data => {
this.update_chart_object();
this.data = data;
this.render();
});
}
}
];
this.set_chart_actions(actions);
}
set_chart_actions(actions) {
this.chart_actions = $(`<div class="chart-actions btn-group dropdown pull-right">
<a class="dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"> <button class="btn btn-default btn-xs"><span class="caret"></span></button>
</a>
<ul class="dropdown-menu" style="max-height: 300px; overflow-y: auto;">
${actions.map(action => `<li><a data-action="${action.action}">${action.label}</a></li>`).join('')}
</ul>
</div>
`);
this.chart_actions.find("a[data-action]").each((i, o) => {
const action = o.dataset.action;
$(o).click(actions.find(a => a.action === action));
});
this.chart_actions.prependTo(this.chart_container);
}
fetch(filters, refresh=false) {
return frappe.xcall(
this.settings.method_path,
{
chart_name: this.chart_doc.name,
filters: filters,
refresh: refresh ? 1 : 0,
}
);
}
render() {
const chart_type_map = {
"Line": "line",
"Bar": "bar",
};
let chart_args = {
title: this.chart_doc.chart_name.bold(),
data: this.data,
type: chart_type_map[this.chart_doc.type],
colors: [this.chart_doc.color || "light-blue"],
axisOptions: {
xIsSeries: this.settings.is_time_series
},
};
if(!this.chart) {
this.chart = new Chart(this.chart_container.find(".chart-wrapper")[0], chart_args);
} else {
this.chart.update(this.data);
}
}
update_last_synced() {
let last_synced_text = __("Last synced {0}", [comment_when(this.chart_doc.last_synced_on)]);
this.container.find(".last-synced-text").html(last_synced_text);
}
update_chart_object() {
frappe.db.get_doc("Dashboard Chart", this.chart_doc.name).then(doc => {
this.chart_doc = doc;
this.prepare_chart_object();
this.update_last_synced();
});
}
prepare_chart_object() {
this.filters = JSON.parse(this.chart_doc.filters_json || '{}');
}
get_settings() {
return new Promise(resolve => frappe.db.get_value("Dashboard Chart Source", this.chart_doc.source, "config", e => {
this.settings = JSON.parse(e.config);
resolve();
}));
}
create_set_filters_dialog() {
const d = new frappe.ui.Dialog({
title: __('Set Filters'),
fields: this.settings.filters
});
d.set_values(this.filters);
d.show();
const set_filters = () => {
const values = d.get_values();
if (!Object.entries(this.filters).map(e => values[e[0]] === e[1]).every(Boolean)) {
frappe.db.set_value("Dashboard Chart", this.chart_doc.name, "filters_json", JSON.stringify(values)).then(() => {
this.fetch(values, true).then(data => {
this.update_chart_object();
this.data = data;
this.render();
});
});
}
d.hide();
};
this.settings.filters.map(field => field.onchange = e => {
if(e) {
d.set_primary_action(__('Save Filters'), set_filters);
}
});
}
}

View file

@ -0,0 +1,19 @@
{
"content": null,
"creation": "2019-01-08 19:19:48.073410",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2019-01-08 19:19:48.073410",
"modified_by": "Administrator",
"module": "Core",
"name": "dashboard",
"owner": "Administrator",
"page_name": "Dashboard",
"roles": [],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "Dashboard"
}

View file

@ -0,0 +1,32 @@
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import json
import frappe
def cache_source(function):
def wrapper(*args, **kwargs):
filters = json.loads(kwargs.get("filters", "{}"))
chart_name = kwargs.get("chart_name")
cache_key = json.dumps({
"name": chart_name,
"filters": filters
}, default=str)
if kwargs.get("refresh"):
results = generate_and_cache_results(chart_name, function, filters, cache_key)
else:
cached_results = frappe.cache().get_value(cache_key)
if cached_results:
results = json.loads(frappe.safe_decode(cached_results))
else:
results = generate_and_cache_results(chart_name, function, filters, cache_key)
return results
return wrapper
def generate_and_cache_results(chart_name, function, filters, cache_key):
results = function(frappe._dict(filters))
frappe.cache().set_value(cache_key, json.dumps(results, default=str))
frappe.db.set_value("Dashboard Chart", chart_name, "last_synced_on", frappe.utils.now())
return results

File diff suppressed because it is too large Load diff

View file

@ -22,13 +22,11 @@ doctype_properties = {
'sort_field': 'Data',
'sort_order': 'Data',
'default_print_format': 'Data',
'read_only_onload': 'Check',
'allow_copy': 'Check',
'istable': 'Check',
'quick_entry': 'Check',
'editable_grid': 'Check',
'max_attachments': 'Int',
'image_view': 'Check',
'track_changes': 'Check',
}

View file

@ -879,6 +879,10 @@ class Database(object):
# implemented in specific class
pass
@staticmethod
def is_column_missing(e):
return frappe.db.is_missing_column(e)
def get_descendants(self, doctype, name):
'''Return descendants of the current record'''
node_location_indexes = self.get_value(doctype, name, ('lft', 'rgt'))

View file

@ -46,6 +46,7 @@ class MariaDBDatabase(Database):
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
'Password': ('varchar', self.VARCHAR_LEN),
'Select': ('varchar', self.VARCHAR_LEN),
'Rating': ('int', '1'),
'Read Only': ('varchar', self.VARCHAR_LEN),
'Attach': ('text', ''),
'Attach Image': ('text', ''),

View file

@ -51,6 +51,7 @@ class PostgresDatabase(Database):
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
'Password': ('varchar', self.VARCHAR_LEN),
'Select': ('varchar', self.VARCHAR_LEN),
'Rating': ('smallint', None),
'Read Only': ('varchar', self.VARCHAR_LEN),
'Attach': ('text', ''),
'Attach Image': ('text', ''),

View file

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Dashboard', {
refresh: function(frm) {
frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name));
}
});

View file

@ -0,0 +1,132 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:dashboard_name",
"beta": 0,
"creation": "2019-01-10 12:54:40.938705",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "dashboard_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Dashboard Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "charts",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Charts",
"length": 0,
"no_copy": 0,
"options": "Dashboard Chart Link",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-12 15:01:12.183208",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "dashboard_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class Dashboard(Document):
pass

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestDashboard(unittest.TestCase):
pass

View file

@ -0,0 +1,69 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Dashboard Chart', {
refresh: function(frm) {
frm.set_df_property("filters_section", "hidden", 1);
frm.trigger("show_filters");
},
source: function(frm) {
frm.trigger("show_filters");
},
show_filters: function(frm) {
if (frm.doc.source) {
if (frm.filters) {
frm.trigger('render_filters_table');
} else {
frappe.db.get_value("Dashboard Chart Source", frm.doc.source, "config", e => {
frm.filters = JSON.parse(e.config).filters;
frm.trigger('render_filters_table');
});
}
}
},
render_filters_table: function(frm) {
frm.set_df_property("filters_section", "hidden", 0);
let fields = frm.filters;
let wrapper = $(frm.get_field('filters_json').wrapper).empty();
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
<thead>
<tr>
<th>${__('Filter')}</th>
<th>${__('Value')}</th>
</tr>
</thead>
<tbody></tbody>
</table>`).appendTo(wrapper);
$(`<p class="text-muted small">${__("Click table to edit")}</p>`).appendTo(wrapper);
let filters = JSON.parse(frm.doc.filters_json || '{}');
fields.map( f => {
const filter_row = $(`<tr><td>${f.label}</td><td>${filters[f.fieldname] || ""}</td></tr>`);
table.find('tbody').append(filter_row);
});
table.on('click', () => {
let dialog = new frappe.ui.Dialog({
title: __('Set Filters'),
fields: fields,
primary_action: function() {
let values = this.get_values();
if(values) {
this.hide();
frm.set_value('filters_json', JSON.stringify(values));
frm.trigger('show_filters');
}
},
primary_action_label: "Set"
});
dialog.show();
dialog.set_values(filters);
});
}
});

View file

@ -0,0 +1,432 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:chart_name",
"beta": 0,
"creation": "2019-01-10 12:28:06.282875",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "chart_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Chart Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "source",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Chart Source",
"length": 0,
"no_copy": 0,
"options": "Dashboard Chart Source",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "filters_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Filters",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "filters_json",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Filters JSON",
"length": 0,
"no_copy": 0,
"options": "JSON",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "chart_options_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Chart Options",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Type",
"length": 0,
"no_copy": 0,
"options": "Line\nBar",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "width",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Width",
"length": 0,
"no_copy": 0,
"options": "Half\nFull",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "color",
"fieldtype": "Color",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Color",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "last_synced_on",
"fieldtype": "Datetime",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Last Synced On",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-18 11:05:51.316816",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class DashboardChart(Document):
pass

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestDashboardChart(unittest.TestCase):
pass

View file

@ -0,0 +1,77 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-03-12 15:00:57.052684",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "chart",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Chart",
"length": 0,
"no_copy": 0,
"options": "Dashboard Chart",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-03-12 15:01:31.639414",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart Link",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class DashboardChartLink(Document):
pass

View file

@ -0,0 +1,5 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Dashboard Chart Source', {
});

View file

@ -0,0 +1,166 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:source_name",
"beta": 0,
"creation": "2019-02-06 07:55:29.579840",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "source_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Source Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "module",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Module",
"length": 0,
"no_copy": 0,
"options": "Module Def",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "config",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Config",
"length": 0,
"no_copy": 0,
"options": "JSON",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-15 14:57:17.696656",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart Source",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.modules.export_file import export_to_files
class DashboardChartSource(Document):
def validate(self):
if frappe.session.user != "Administrator":
frappe.throw(_("Only Administrator is allowed to create Dashboard Chart Sources"))
def on_update(self):
export_to_files(record_list=[[self.doctype, self.name]], record_module=self.module, create_init=True)

View file

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestDashboardChartSource(unittest.TestCase):
pass

View file

@ -26,8 +26,27 @@ def set_list_settings(doctype, values):
doc.save()
@frappe.whitelist()
def get_user_assignments_and_count():
user_list = frappe.get_list("User", filters={"user_type": "System User"})
assignment_data = sorted([{"count":frappe.db.count('ToDo', filters = {'reference_type': 'Issue', 'owner': user['name'], 'status': 'Open'}),
"name": user['name']} for user in user_list], key=lambda k: k['count'], reverse = True)
return assignment_data
def get_user_assignments_and_count(doctype, current_filters):
subquery_condition = ''
if current_filters:
# get the subquery
subquery = frappe.get_all(doctype,
filters=current_filters, return_query = True)
subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery)
todo_list = frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
from
`tabToDo`, `tabUser`
where
`tabToDo`.status='Open' and
`tabToDo`.owner = `tabUser`.name and
`tabUser`.user_type = 'System User'
{subquery_condition}
group by
`tabToDo`.owner
order by
count desc
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)
return todo_list

View file

@ -275,14 +275,14 @@ def build_xlsx_data(columns, data, visible_idx):
if i in visible_idx:
row_data = []
if isinstance(row, list):
row_data = row
elif isinstance(row, dict) and row:
if isinstance(row, dict) and row:
for idx in range(len(data.columns)):
label = columns[idx]["label"]
fieldname = columns[idx]["fieldname"]
row_data.append(row.get(fieldname, row.get(label, "")))
else:
row_data = row
result.append(row_data)

View file

@ -1712,9 +1712,10 @@
},
"Mozambique": {
"code": "mz",
"currency": "MZN",
"currency_fraction": "Centavo",
"currency_fraction_units": 100,
"currency_symbol": "MT",
"currency_symbol": "MZN",
"number_format": "#,###.##",
"timezones": [
"Africa/Maputo"
@ -2690,8 +2691,11 @@
},
"Yemen": {
"code": "ye",
"currency": "YER",
"currency_fraction": "Fils",
"currency_fraction_units": 100,
"smallest_currency_fraction_value": 0.01,
"currency_name": "Yemeni Rial",
"currency_symbol": "\ufdfc",
"number_format": "#,###.##",
"timezones": [

View file

@ -155,11 +155,14 @@ def upload_from_folder(path, is_private, dropbox_folder, dropbox_client, did_not
found = False
for file_metadata in response.entries:
if (os.path.basename(filepath) == file_metadata.name
and os.stat(encode(filepath)).st_size == int(file_metadata.size)):
found = True
update_file_dropbox_status(f.name)
break
try:
if (os.path.basename(filepath) == file_metadata.name
and os.stat(encode(filepath)).st_size == int(file_metadata.size)):
found = True
update_file_dropbox_status(f.name)
break
except Exception:
error_log.append(frappe.get_traceback())
if not found:
try:

View file

@ -22,11 +22,11 @@ def create_gsuite_doc(doctype, docname, gs_template=None):
json_data = doc.as_dict()
filename = templ.document_name.format(**json_data)
r = run_gsuite_script('new', filename, templ.template_id, templ.destination_id, json_data)
response = run_gsuite_script('new', filename, templ.template_id, templ.destination_id, json_data)
_file = frappe.get_doc({
"doctype": "File",
"file_url": r['url'],
"file_url": response['url'],
"file_name": filename,
"attached_to_doctype": doctype,
"attached_to_name": docname,
@ -36,15 +36,15 @@ def create_gsuite_doc(doctype, docname, gs_template=None):
comment = frappe.get_doc(doctype, docname).add_comment("Attachment",
_("added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>{icon}".format(**{
"icon": ' <i class="fa fa-lock text-warning"></i>' if filedata.is_private else "",
"file_url": filedata.file_url.replace("#", "%23") if filedata.file_name else filedata.file_url,
"file_name": filedata.file_name or filedata.file_url
"icon": ' <i class="fa fa-lock text-warning"></i>' if _file.is_private else "",
"file_url": _file.file_url.replace("#", "%23") if _file.file_name else _file.file_url,
"file_name": _file.file_name or _file.file_url
})))
return {
"name": filedata.name,
"file_name": filedata.file_name,
"file_url": filedata.file_url,
"is_private": filedata.is_private,
"name": _file.name,
"file_name": _file.file_name,
"file_url": _file.file_url,
"is_private": _file.is_private,
"comment": comment.as_dict() if comment else {}
}

View file

@ -27,6 +27,7 @@ data_fieldtypes = (
'Dynamic Link',
'Password',
'Select',
'Rating',
'Read Only',
'Attach',
'Attach Image',

View file

@ -60,15 +60,24 @@ def set_user_and_static_default_values(doc):
allowed_records = get_allowed_docs_for_doctype(doctype_user_permissions, df.parent)
user_default_value = get_user_default_value(df, defaults, doctype_user_permissions, allowed_records)
if user_default_value is not None:
doc.set(df.fieldname, user_default_value)
if user_default_value != None:
# do not set default if the field on which current field is dependent is not set
if is_dependent_field_set(df.depends_on, doc):
doc.set(df.fieldname, user_default_value)
else:
if df.fieldname != doc.meta.title_field:
static_default_value = get_static_default_value(df, doctype_user_permissions, allowed_records)
if static_default_value is not None:
if static_default_value != None and is_dependent_field_set(df.depends_on, doc):
doc.set(df.fieldname, static_default_value)
def is_dependent_field_set(fieldname, doc):
value_dict = doc.as_dict()
if not fieldname: return True
# to check if fieldname passed is valid
if fieldname not in value_dict: return True
return value_dict[fieldname]
def get_user_default_value(df, defaults, doctype_user_permissions, allowed_records):
# don't set defaults for "User" link field using User Permissions!
if df.fieldtype == "Link" and df.options != "User":

View file

@ -36,7 +36,7 @@ class DatabaseQuery(object):
ignore_permissions=False, user=None, with_comment_count=False,
join='left join', distinct=False, start=None, page_length=None, limit=None,
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
update=None, add_total_row=None, user_settings=None, reference_doctype=None):
update=None, add_total_row=None, user_settings=None, reference_doctype=None, return_query=False):
if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user):
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
raise frappe.PermissionError(self.doctype)
@ -79,6 +79,7 @@ class DatabaseQuery(object):
self.user = user or frappe.session.user
self.update = update
self.user_settings_fields = copy.deepcopy(self.fields)
self.return_query = return_query
# for contextual user permission check
# to determine which user permission is applicable on link field of specific doctype
@ -91,6 +92,8 @@ class DatabaseQuery(object):
result = self.run_custom_query(query)
else:
result = self.build_and_run()
if return_query:
return result
if with_comment_count and not as_list and self.doctype:
self.add_comment_count(result)
@ -115,7 +118,10 @@ class DatabaseQuery(object):
query = """select %(fields)s from %(tables)s %(conditions)s
%(group_by)s %(order_by)s %(limit)s""" % args
return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug, update=self.update)
if self.return_query:
return query
else:
return frappe.db.sql(query, as_dict=not self.as_list, debug=self.debug, update=self.update)
def prepare_args(self):
self.parse_args()

View file

@ -45,6 +45,7 @@ type_map = {
'Dynamic Link': ('varchar', varchar_len),
'Password': ('varchar', varchar_len),
'Select': ('varchar', varchar_len),
'Rating': ('int', '1'),
'Read Only': ('varchar', varchar_len),
'Attach': ('text', ''),
'Attach Image': ('text', ''),

View file

@ -213,12 +213,12 @@ class Document(BaseDocument):
self.set_docstatus()
self.check_if_latest()
self.run_method("before_insert")
self._validate_links()
self.set_new_name()
self.set_parent_in_children()
self.validate_higher_perm_levels()
self.flags.in_insert = True
self._validate_links()
self.run_before_save_methods()
self._validate()
self.set_docstatus()

View file

@ -88,21 +88,21 @@ def set_name_by_naming_series(doc):
def make_autoname(key='', doctype='', doc=''):
"""
Creates an autoname from the given key:
Creates an autoname from the given key:
**Autoname rules:**
**Autoname rules:**
* The key is separated by '.'
* '####' represents a series. The string before this part becomes the prefix:
Example: ABC.#### creates a series ABC0001, ABC0002 etc
* 'MM' represents the current month
* 'YY' and 'YYYY' represent the current year
* The key is separated by '.'
* '####' represents a series. The string before this part becomes the prefix:
Example: ABC.#### creates a series ABC0001, ABC0002 etc
* 'MM' represents the current month
* 'YY' and 'YYYY' represent the current year
*Example:*
* DE/./.YY./.MM./.##### will create a series like
DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
* DE/./.YY./.MM./.##### will create a series like
DE/09/01/0001 where 09 is the year, 01 is the month and 0001 is the series
"""
if key == "hash":
return frappe.generate_hash(doctype, 10)
@ -121,7 +121,6 @@ def parse_naming_series(parts, doctype='', doc=''):
n = ''
if isinstance(parts, string_types):
parts = parts.split('.')
series_set = False
today = now_datetime()
for e in parts:
@ -129,7 +128,7 @@ def parse_naming_series(parts, doctype='', doc=''):
if e.startswith('#'):
if not series_set:
digits = len(e)
part = getseries(n, digits, doctype)
part = getseries(n, digits)
series_set = True
elif e == 'YY':
part = today.strftime('%y')
@ -141,6 +140,9 @@ def parse_naming_series(parts, doctype='', doc=''):
part = today.strftime('%Y')
elif e == 'FY':
part = frappe.defaults.get_user_default("fiscal_year")
elif e.startswith('{') and doc:
e = e.replace('{', '').replace('}', '')
part = doc.get(e)
elif doc and doc.get(e):
part = doc.get(e)
else:
@ -152,7 +154,7 @@ def parse_naming_series(parts, doctype='', doc=''):
return n
def getseries(key, digits, doctype=''):
def getseries(key, digits):
# series created ?
current = frappe.db.sql("SELECT `current` FROM `tabSeries` WHERE `name`=%s FOR UPDATE", (key,))
if current and current[0][0] is not None:

View file

@ -68,7 +68,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F
"""walk and sync all doctypes and pages"""
# load in sequence - warning for devs
document_types = ['doctype', 'page', 'report', 'print_format',
document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format',
'website_theme', 'web_form', 'notification', 'print_style',
'data_migration_mapping', 'data_migration_plan']
for doctype in document_types:

View file

@ -1,4 +1,4 @@
frappe.patches.v12_0.remove_deprecated_fields_from_doctype #2
frappe.patches.v12_0.remove_deprecated_fields_from_doctype #3
execute:frappe.db.sql("""update `tabPatch Log` set patch=replace(patch, '.4_0.', '.v4_0.')""") #2014-05-12
frappe.patches.v5_0.convert_to_barracuda_and_utf8mb4
execute:frappe.utils.global_search.setup_global_search_table()

View file

@ -5,3 +5,8 @@ def execute():
frappe.model.delete_fields({
'DocType': ['hide_heading', 'image_view', 'read_only_onload']
}, delete=1)
frappe.db.sql('''
DELETE from `tabProperty Setter`
WHERE property = 'read_only_onload'
''')

View file

@ -6,12 +6,11 @@
"public/less/avatar.less",
"node_modules/highlight.js/styles/zenburn.css",
"public/less/form.less",
"public/less/controls.less",
"public/less/chat.less",
"public/less/form_grid.less",
"node_modules/frappe-datatable/dist/frappe-datatable.css"
],
"css/frappe-web-b4.css": [
"public/css/font-awesome.css",
"public/less/indicator.less",
"public/scss/website.scss"
],
@ -93,7 +92,8 @@
"public/js/frappe/form/controls/geolocation.js",
"public/js/frappe/form/controls/multiselect.js",
"public/js/frappe/form/controls/multicheck.js",
"public/js/frappe/form/controls/table_multiselect.js"
"public/js/frappe/form/controls/table_multiselect.js",
"public/js/frappe/form/controls/rating.js"
],
"js/dialog.min.js": [
"public/js/frappe/dom.js",
@ -375,7 +375,8 @@
"public/less/quill.less",
"public/less/datepicker.less",
"public/less/awesomplete.less",
"public/less/form_grid.less"
"public/less/form_grid.less",
"public/less/controls.less"
],
"js/print_format_v3.min.js": [
"public/js/legacy/layout.js",

View file

@ -178,4 +178,4 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({
$(this.disp_area).toggleClass("bold", !!(this.df.bold || this.df.reqd));
}
}
});
});

View file

@ -32,7 +32,8 @@ frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({
'HTML': 'ace/mode/html',
'CSS': 'ace/mode/css',
'Markdown': 'ace/mode/markdown',
'SCSS': 'ace/mode/scss'
'SCSS': 'ace/mode/scss',
'JSON': 'ace/mode/json'
};
const language = this.df.options;

View file

@ -0,0 +1,62 @@
frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({
make_input() {
this._super();
const star_template = `
<div class="rating">
<i class="fa fa-fw fa-star" data-idx=1></i>
<i class="fa fa-fw fa-star" data-idx=2></i>
<i class="fa fa-fw fa-star" data-idx=3></i>
<i class="fa fa-fw fa-star" data-idx=4></i>
<i class="fa fa-fw fa-star" data-idx=5></i>
</div>
`;
$(this.input_area).html(star_template);
$(this.input_area).find('i').hover((ev) => {
const el = $(ev.currentTarget);
let star_value = el.data('idx');
el.parent().children('i.fa').each( function(e){
if (e < star_value) {
$(this).addClass('star-hover');
} else {
$(this).removeClass('star-hover');
}
});
}, (ev) => {
const el = $(ev.currentTarget);
el.parent().children('i.fa').each( function() {
$(this).removeClass('star-hover');
});
});
$(this.input_area).find('i').click((ev) => {
const el = $(ev.currentTarget);
let star_value = el.data('idx');
el.parent().children('i.fa').each( function(e) {
if (e < star_value){
$(this).addClass('star-click');
} else {
$(this).removeClass('star-click');
}
});
this.validate_and_set_in_model(star_value, ev);
if (this.doctype && this.docname) {
this.set_input(star_value);
}
});
},
get_value() {
return cint(this.value);
},
set_formatted_input(value) {
let el = $(this.input_area).find('i');
el.children('i.fa').prevObject.each( function(e) {
if (e < value) {
$(this).addClass('star-click');
} else {
$(this).removeClass('star-click');
}
});
}
});

View file

@ -52,6 +52,13 @@ frappe.form.formatters = {
Percent: function(value, docfield, options) {
return frappe.form.formatters._right(flt(value, 2) + "%", options)
},
Rating: function(value) {
return `<span class="rating">
${Array.from(new Array(5)).map((_, i) =>
`<i class="fa fa-fw fa-star ${i < (value || 0) ? "star-click": "" } star-icon" data-idx="${(i+1)}"></i>`
).join('')}
</span>`;
},
Currency: function (value, docfield, options, doc) {
var currency = frappe.meta.get_field_currency(docfield, doc);
var precision = docfield.precision || cint(frappe.boot.sysdefaults.currency_precision) || 2;

View file

@ -45,11 +45,15 @@
</ul>
</div>
</li>
<li class="assigned-to">
<li class="assigned-to" style="display: none">
<a class = "dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{%= __("Assigned To") %} <span class="caret"></span>
</a>
<ul class="dropdown-menu assigned-dropdown" style="max-height: 300px; overflow-y: auto;">
<li><div class="list-loading text-center assigned-loading text-muted">
{%= (__("Loading") + "..." ) %}
</div>
</li>
</ul>
</li>
{% if(frappe.help.has_help(doctype)) { %}

View file

@ -26,7 +26,6 @@ frappe.views.ListSidebar = class ListSidebar {
this.setup_reports();
this.setup_list_filter();
this.setup_assigned_to();
this.setup_views();
this.setup_kanban_boards();
this.setup_calendar_view();
@ -37,6 +36,15 @@ frappe.views.ListSidebar = class ListSidebar {
if (limits.upgrade_url && limits.expiry && !frappe.flags.upgrade_dismissed) {
this.setup_upgrade_box();
}
if(this.doctype !== 'ToDo') {
$('.assigned-to').show();
}
$('.assigned-to').on('click', () => {
$('.assigned').remove();
this.setup_assigned_to();
});
}
setup_views() {
@ -217,14 +225,18 @@ frappe.views.ListSidebar = class ListSidebar {
}
setup_assigned_to() {
$('.assigned-loading').show();
let dropdown = this.page.sidebar.find('.assigned-dropdown');
if(this.doctype === 'ToDo') {
$('.assigned-to').remove();
}
frappe.call('frappe.desk.listview.get_user_assignments_and_count').then((data) => {
let current_user_count = data.message.find(user => user.name === frappe.session.user).count;
this.get_html_for_assigned(frappe.session.user, current_user_count).appendTo(dropdown);
let user_list = data.message.filter(user => !['Guest', frappe.session.user, 'Administrator'].includes(user.name));
let current_filters = this.list_view.get_filters_for_args();
frappe.call('frappe.desk.listview.get_user_assignments_and_count', {doctype: this.doctype, current_filters: current_filters}).then((data) => {
$('.assigned-loading').hide();
let current_user = data.message.find(user => user.name === frappe.session.user);
if(current_user) {
let current_user_count = current_user.count;
this.get_html_for_assigned(frappe.session.user, current_user_count).appendTo(dropdown);
}
let user_list = data.message.filter(user => !['Guest', frappe.session.user, 'Administrator'].includes(user.name) && user.count!==0 );
user_list.forEach((user) => {
this.get_html_for_assigned(user.name, user.count).appendTo(dropdown);
});

View file

@ -923,7 +923,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
get_checked_items(only_docnames) {
const docnames = Array.from(this.$checks || [])
.map(check => $(check).data().name);
.map(check => cstr($(check).data().name));
if (only_docnames) return docnames;

View file

@ -238,7 +238,7 @@ frappe.ui.Slides = class Slides {
.appendTo(this.container);
this.$body = $(`<div>`).addClass(`slide-container`)
.appendTo(this.container);
this.$footer = $(`<div>`).addClass(`footer`)
this.$footer = $(`<div>`).addClass(`slide-footer`)
.appendTo(this.container);
this.render_progress_dots();

View file

@ -25,10 +25,12 @@ frappe.views.CalendarView = class CalendarView extends frappe.views.ListView {
}
setup_defaults() {
super.setup_defaults();
this.page_title = __('{0} Calendar', [this.page_title]);
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
this.calendar_name = frappe.get_route()[3];
return super.setup_defaults()
.then(() => {
this.page_title = __('{0} Calendar', [this.page_title]);
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
this.calendar_name = frappe.get_route()[3];
});
}
setup_view() {

View file

@ -40,15 +40,17 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
}
setup_defaults() {
super.setup_defaults();
this.page_title = __('File Manager');
return super.setup_defaults()
.then(() => {
this.page_title = __('File Manager');
const route = frappe.get_route();
this.current_folder = route.slice(2).join('/');
this.filters = [['File', 'folder', '=', this.current_folder, true]];
this.order_by = this.view_user_settings.order_by || 'file_name asc';
const route = frappe.get_route();
this.current_folder = route.slice(2).join('/');
this.filters = [['File', 'folder', '=', this.current_folder, true]];
this.order_by = this.view_user_settings.order_by || 'file_name asc';
this.menu_items = this.menu_items.concat(this.file_menu_items());
this.menu_items = this.menu_items.concat(this.file_menu_items());
});
}
file_menu_items() {

View file

@ -6,16 +6,18 @@ frappe.views.GanttView = class GanttView extends frappe.views.ListView {
}
setup_defaults() {
super.setup_defaults();
this.page_title = this.page_title + ' ' + __('Gantt');
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
if(this.calendar_settings.order_by) {
this.sort_by = this.calendar_settings.order_by;
this.sort_order = 'asc';
} else {
this.sort_by = this.view_user_settings.sort_by || this.calendar_settings.field_map.start;
this.sort_order = this.view_user_settings.sort_order || 'asc';
}
return super.setup_defaults()
.then(() => {
this.page_title = this.page_title + ' ' + __('Gantt');
this.calendar_settings = frappe.views.calendar[this.doctype] || {};
if(this.calendar_settings.order_by) {
this.sort_by = this.calendar_settings.order_by;
this.sort_order = 'asc';
} else {
this.sort_by = this.view_user_settings.sort_by || this.calendar_settings.field_map.start;
this.sort_order = this.view_user_settings.sort_order || 'asc';
}
})
}
setup_view() {

View file

@ -9,8 +9,10 @@ frappe.views.ImageView = class ImageView extends frappe.views.ListView {
}
setup_defaults() {
super.setup_defaults();
this.page_title = this.page_title + ' ' + __('Images');
return super.setup_defaults()
.then(() => {
this.page_title = this.page_title + ' ' + __('Images');
})
}
setup_view() {

View file

@ -27,19 +27,21 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
}
setup_defaults() {
super.setup_defaults();
this.board_name = frappe.get_route()[3];
this.page_title = this.board_name;
this.card_meta = this.get_card_meta();
return super.setup_defaults()
.then(() => {
this.board_name = frappe.get_route()[3];
this.page_title = this.board_name;
this.card_meta = this.get_card_meta();
this.menu_items.push({
label: __('Save filters'),
action: () => {
this.save_kanban_board_filters();
}
});
this.menu_items.push({
label: __('Save filters'),
action: () => {
this.save_kanban_board_filters();
}
});
return this.get_board();
return this.get_board();
});
}
get_board() {

View file

@ -493,10 +493,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
const type = chart_type.toLowerCase();
const colors = color ? [color] : undefined;
let labels = get_column_values(x_field)
.filter(Boolean)
.map(d => d.trim())
.filter(Boolean);
let labels = get_column_values(x_field);
let dataset_values = get_column_values(y_field).map(d => Number(d));

View file

@ -77,3 +77,15 @@
margin-right: 5px;
margin-bottom: 5px;
}
.rating {
i {
color: @text-extra-muted;
}
.star-hover {
color: @text-muted;
}
.star-click {
color: @text-light;
}
}

View file

@ -956,8 +956,8 @@ input[type="checkbox"] {
.btn-primary {
font-weight: bold;
}
.footer {
margin: 15px 0;
.slide-footer {
margin: 15px 0px;
padding: 0px 7px;
.btn:not(:last-child) {

View file

@ -66,6 +66,10 @@ img {
text-decoration: underline;
}
.text-extra-muted {
color: #d1d8dd !important;
}
.web-footer {
padding: 5rem 0;
min-height: 140px;
@ -86,4 +90,4 @@ img {
.indicator {
font-size: inherit;
}
}

View file

@ -17,6 +17,7 @@ from six.moves import reload_module
from frappe.model.naming import revert_series_if_last
unittest_runner = unittest.TextTestRunner
SLOW_TEST_THRESHOLD = 2
def xmlrunner_wrapper(output):
"""Convenience wrapper to keep method signature unchanged for XMLTestRunner and TextTestRunner"""
@ -100,7 +101,8 @@ class TimeLoggingTestResult(unittest.TextTestResult):
def addSuccess(self, test):
elapsed = time.time() - self._started_at
name = self.getDescription(test)
self.stream.write("\n{} ({:.03}s)\n".format(name, elapsed))
if elapsed >= SLOW_TEST_THRESHOLD:
self.stream.write("\n{} ({:.03}s)\n".format(name, elapsed))
super(TimeLoggingTestResult, self).addSuccess(test)

View file

@ -4,6 +4,8 @@ from __future__ import unicode_literals
import frappe, unittest
import frappe.desk.form.assign_to
from frappe.desk.listview import get_user_assignments_and_count
from frappe.automation.doctype.assignment_rule.test_assignment_rule import make_note
class TestAssign(unittest.TestCase):
def test_assign(self):
@ -11,12 +13,7 @@ class TestAssign(unittest.TestCase):
if not frappe.db.exists("User", "test@example.com"):
frappe.get_doc({"doctype":"User", "email":"test@example.com", "first_name":"Test"}).insert()
added = frappe.desk.form.assign_to.add({
"assign_to": "test@example.com",
"doctype": todo.doctype,
"name": todo.name,
"description": todo.description,
})
added = assign(todo, "test@example.com")
self.assertTrue("test@example.com" in [d.owner for d in added])
@ -25,3 +22,46 @@ class TestAssign(unittest.TestCase):
# assignment is cleared
assignments = frappe.desk.form.assign_to.get(dict(doctype = todo.doctype, name=todo.name))
self.assertEqual(len(assignments), 0)
def test_assignment_count(self):
frappe.db.sql('delete from tabToDo')
if not frappe.db.exists("User", "test_assign1@example.com"):
frappe.get_doc({"doctype":"User", "email":"test_assign1@example.com", "first_name":"Test", "roles": [{"role": "System Manager"}]}).insert()
if not frappe.db.exists("User", "test_assign2@example.com"):
frappe.get_doc({"doctype":"User", "email":"test_assign2@example.com", "first_name":"Test", "roles": [{"role": "System Manager"}]}).insert()
note = make_note()
assign(note, "test_assign1@example.com")
note = make_note(dict(public=1))
assign(note, "test_assign2@example.com")
note = make_note(dict(public=1))
assign(note, "test_assign2@example.com")
note = make_note()
assign(note, "test_assign2@example.com")
data = {d.name: d.count for d in get_user_assignments_and_count('Note', [])}
self.assertTrue('test_assign1@example.com' in data)
self.assertEqual(data['test_assign1@example.com'], 1)
self.assertEqual(data['test_assign2@example.com'], 3)
data = {d.name: d.count for d in get_user_assignments_and_count('Note', [{'public': 1}])}
self.assertFalse('test_assign1@example.com' in data)
self.assertEqual(data['test_assign2@example.com'], 2)
frappe.db.rollback()
def assign(doc, user):
return frappe.desk.form.assign_to.add({
"assign_to": user,
"doctype": doc.doctype,
"name": doc.name,
"description": 'test',
})

View file

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe, unittest, os
from frappe.utils import cint
from frappe.model.naming import revert_series_if_last, make_autoname, parse_naming_series
from frappe.utils.testutils import add_custom_field, clear_custom_fields
class TestDocument(unittest.TestCase):
def test_get_return_empty_list_for_table_field_if_none(self):
@ -236,3 +237,20 @@ class TestDocument(unittest.TestCase):
new_current = cint(frappe.db.get_value('Series', prefix, "current", order_by="name"))
self.assertEqual(cint(old_current) - 1, new_current)
def test_default_of_dependent_field(self):
add_custom_field('ToDo', 'parent_field', 'Data')
add_custom_field('ToDo', 'dependent_field', 'Data',
default='Some Data', depends_on='parent_field')
add_custom_field('ToDo', 'independent_field', 'Data',
default='Some Data')
doc = frappe.new_doc('ToDo')
self.assertFalse(doc.get('dependent_field'))
self.assertEqual(doc.get('independent_field'), 'Some Data')
clear_custom_fields('ToDo')

View file

@ -51,7 +51,7 @@ class TestNaming(unittest.TestCase):
todo.description = description
todo.insert()
series = getseries('', 2, doctype)
series = getseries('', 2)
series = str(int(series)-1)
@ -93,4 +93,4 @@ class TestNaming(unittest.TestCase):
count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0]
self.assertEqual(count.get('current'), 2)
frappe.db.sql("""delete from `tabSeries` where name = %s""", series)
frappe.db.sql("""delete from `tabSeries` where name = %s""", series)

View file

@ -4,13 +4,15 @@ from __future__ import unicode_literals
import frappe
def add_custom_field(doctype, fieldname, fieldtype='Data', options=None):
def add_custom_field(doctype, fieldname, fieldtype='Data', options=None, default=None, depends_on=None):
frappe.get_doc({
"doctype": "Custom Field",
"dt": doctype,
"fieldname": fieldname,
"fieldtype": fieldtype,
"options": options
"options": options,
"default": default,
"depends_on": depends_on
}).insert()
def clear_custom_fields(doctype):

View file

@ -19,6 +19,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "fieldname",
"fieldtype": "Select",
"hidden": 0,
@ -50,6 +51,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "fieldtype",
"fieldtype": "Select",
"hidden": 0,
@ -62,7 +64,7 @@
"label": "Fieldtype",
"length": 0,
"no_copy": 0,
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nLink\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break\nPage Break",
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break\nPage Break",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@ -82,6 +84,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "label",
"fieldtype": "Data",
"hidden": 0,
@ -114,6 +117,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.fieldtype === 'Link'",
"fetch_if_empty": 0,
"fieldname": "allow_read_on_all_link_options",
"fieldtype": "Check",
"hidden": 0,
@ -146,6 +150,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "reqd",
"fieldtype": "Check",
"hidden": 0,
@ -177,6 +182,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "depends_on",
"fieldtype": "Code",
"hidden": 0,
@ -209,6 +215,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "read_only",
"fieldtype": "Check",
"hidden": 0,
@ -240,6 +247,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "show_in_filter",
"fieldtype": "Check",
"hidden": 0,
@ -272,6 +280,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "hidden",
"fieldtype": "Check",
"hidden": 0,
@ -303,6 +312,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
@ -333,6 +343,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "options",
"fieldtype": "Text",
"hidden": 0,
@ -364,6 +375,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "max_length",
"fieldtype": "Int",
"hidden": 0,
@ -397,6 +409,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.fieldtype=='Int'",
"fetch_if_empty": 0,
"fieldname": "max_value",
"fieldtype": "Int",
"hidden": 0,
@ -429,6 +442,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
@ -459,6 +473,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Text",
"hidden": 0,
@ -490,6 +505,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_8",
"fieldtype": "Column Break",
"hidden": 0,
@ -520,6 +536,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "default",
"fieldtype": "Data",
"hidden": 0,
@ -555,7 +572,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-01-31 15:06:55.367043",
"modified": "2019-03-12 17:00:50.034229",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form Field",