Merge branch 'develop' of https://github.com/frappe/frappe into website-chat

This commit is contained in:
Achilles Rasquinha 2018-03-26 16:01:55 +05:30
commit 5ed33335de
30 changed files with 381 additions and 248 deletions

View file

@ -15,7 +15,7 @@ from past.builtins import cmp
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template
__version__ = '10.1.6'
__version__ = '10.1.10'
__title__ = "Frappe Framework"
local = Local()

View file

@ -88,7 +88,7 @@ def make(doctype=None, name=None, content=None, subject=None, sent_or_received =
frappe.db.commit()
if cint(send_email):
frappe.flags.print_letterhead = print_letterhead
frappe.flags.print_letterhead = cint(print_letterhead)
comm.send(print_html, print_format, attachments, send_me_a_copy=send_me_a_copy)
return {

View file

@ -29,7 +29,7 @@ def export_data(doctype=None, parent_doctype=None, all_doctypes=True, with_data=
select_columns=select_columns, file_type=file_type, template=template, filters=filters)
exporter.build_response()
class DataExporter():
class DataExporter:
def __init__(self, doctype=None, parent_doctype=None, all_doctypes=True, with_data=False,
select_columns=None, file_type='CSV', template=False, filters=None):
self.doctype = doctype
@ -98,10 +98,9 @@ class DataExporter():
self.add_data()
if self.with_data and not self.data:
frappe.respond_as_web_page(_('No Data'), _('There is no data to be exported'), indicator_color='orange')
return
if self.file_type == 'Excel':
return self.build_response_as_excel()
self.build_response_as_excel()
else:
# write out response as a type csv
frappe.response['result'] = cstr(self.writer.getvalue())
@ -323,7 +322,7 @@ class DataExporter():
def build_response_as_excel(self):
filename = frappe.generate_hash("", 10)
with open(filename, 'wb') as f:
f.write(cstr(self.writer.getvalue()).encode("utf-8"))
f.write(cstr(self.writer.getvalue()).encode('utf-8'))
f = open(filename)
reader = csv.reader(f)

View file

@ -34,6 +34,9 @@ class TestTransactionLog(unittest.TestCase):
sha = hashlib.sha256()
sha.update(str(third_log.transaction_hash) + str(second_log.chaining_hash))
sha.update(
frappe.safe_encode(str(third_log.transaction_hash)) +
frappe.safe_encode(str(second_log.chaining_hash))
)
self.assertEqual(sha.hexdigest(), third_log.chaining_hash)

View file

@ -68,6 +68,7 @@ class User(Document):
self.validate_user_email_inbox()
ask_pass_update()
self.validate_roles()
self.validate_user_image()
if self.language == "Loading...":
self.language = None
@ -81,6 +82,10 @@ class User(Document):
self.set('roles', [])
self.append_roles(*[role.role for role in role_profile.roles])
def validate_user_image(self):
if self.user_image and len(self.user_image) > 2000:
frappe.throw(_("Not a valid User Image."))
def on_update(self):
# clear new password
self.validate_user_limit()

View file

@ -17,6 +17,7 @@ from frappe import _
from frappe.model.utils.link_count import flush_local_link_count
from frappe.utils.background_jobs import execute_job, get_queue
from frappe import as_unicode
import six
# imports - compatibility imports
from six import (
@ -81,10 +82,14 @@ class Database:
conversions.update({
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,
TimeDelta: conversions[binary_type],
UnicodeWithAttrs: conversions[text_type]
})
if six.PY2:
conversions.update({
TimeDelta: conversions[binary_type]
})
if usessl:
self._conn = pymysql.connect(self.host, self.user or '', self.password or '',
charset='utf8mb4', use_unicode = True, ssl=self.ssl, conv = conversions, local_infile = self.local_infile)

View file

@ -3,6 +3,7 @@
frappe.ui.form.on('Dropbox Settings', {
refresh: function(frm) {
frm.toggle_display(["app_access_key", "app_secret_key"], !(frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config));
frm.clear_custom_buttons();
frm.events.take_backup(frm);
},
@ -19,7 +20,7 @@ frappe.ui.form.on('Dropbox Settings', {
}
})
}
else if (frm.doc.dropbox_setup_via_site_config) {
else if (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config) {
frappe.call({
method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.get_redirect_url",
freeze: true,
@ -36,7 +37,7 @@ frappe.ui.form.on('Dropbox Settings', {
},
take_backup: function(frm) {
if ((frm.doc.app_access_key && frm.doc.app_secret_key) || frm.doc.dropbox_setup_via_site_config){
if ((frm.doc.app_access_key && frm.doc.app_secret_key) || (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config)){
frm.add_custom_button(__("Take Backup Now"), function(frm){
frappe.call({
method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backup",

View file

@ -54,7 +54,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Send Notifications To",
"length": 0,
@ -84,7 +84,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Backup Frequency",
"length": 0,
@ -108,7 +108,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.dropbox_setup_via_site_config",
"depends_on": "",
"fieldname": "app_access_key",
"fieldtype": "Data",
"hidden": 0,
@ -139,7 +139,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:!doc.dropbox_setup_via_site_config",
"depends_on": "",
"fieldname": "app_secret_key",
"fieldtype": "Password",
"hidden": 0,
@ -283,36 +283,6 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "dropbox_setup_via_site_config",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dropbox Setup via Site Config",
"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,
"unique": 0
}
],
"has_web_view": 0,
@ -325,7 +295,7 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-20 15:45:33.683827",
"modified": "2018-03-22 16:02:00.597029",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Dropbox Settings",

View file

@ -20,7 +20,8 @@ ignore_list = [".DS_Store"]
class DropboxSettings(Document):
def onload(self):
if not self.app_access_key and frappe.conf.dropbox_access_key:
self.dropbox_setup_via_site_config = 1
self.set_onload("dropbox_setup_via_site_config", 1)
@frappe.whitelist()
def take_backup():
@ -171,7 +172,7 @@ def upload_file_to_dropbox(filename, folder, dropbox_client):
cursor.offset = f.tell()
except dropbox.exceptions.ApiError as e:
if isinstance(e.error, dropbox.files.UploadError):
error = "File Path: {path}\n".foramt(path=path)
error = "File Path: {path}\n".format(path=path)
error += frappe.get_traceback()
frappe.log_error(error)
else:
@ -201,7 +202,7 @@ def get_dropbox_settings(redirect_uri=False):
if redirect_uri:
app_details.update({
'rediret_uri': get_request_site_address(True) \
'redirect_uri': get_request_site_address(True) \
+ '/api/method/frappe.integrations.doctype.dropbox_settings.dropbox_settings.dropbox_auth_finish' \
if settings.app_secret_key else frappe.conf.dropbox_broker_site\
+ '/api/method/dropbox_erpnext_broker.www.setup_dropbox.generate_dropbox_access_token',
@ -233,7 +234,7 @@ def get_dropbox_authorize_url():
dropbox_oauth_flow = dropbox.DropboxOAuth2Flow(
app_details["app_key"],
app_details["app_secret"],
app_details["rediret_uri"],
app_details["redirect_uri"],
{},
"dropbox-auth-csrf-token"
)
@ -254,7 +255,7 @@ def dropbox_auth_finish(return_access_token=False):
dropbox_oauth_flow = dropbox.DropboxOAuth2Flow(
app_details["app_key"],
app_details["app_secret"],
app_details["rediret_uri"],
app_details["redirect_uri"],
{
'dropbox-auth-csrf-token': callback.state
},

View file

@ -23,12 +23,18 @@ class StripeSettings(Document):
"XAF", "XOF", "XPF", "YER", "ZAR"
]
currency_wise_minimum_charge_amount = {
'JPY': 50, 'MXN': 10, 'DKK': 2.50, 'HKD': 4.00, 'NOK': 3.00, 'SEK': 3.00,
'USD': 0.50, 'AUD': 0.50, 'BRL': 0.50, 'CAD': 0.50, 'CHF': 0.50, 'EUR': 0.50,
'GBP': 0.30, 'NZD': 0.50, 'SGD': 0.50
}
def validate(self):
create_payment_gateway('Stripe')
call_hook_method('payment_gateway_enabled', gateway='Stripe')
if not self.flags.ignore_mandatory:
self.validate_stripe_credentails()
def validate_stripe_credentails(self):
if self.publishable_key and self.secret_key:
header = {"Authorization": "Bearer {0}".format(self.get_password(fieldname="secret_key", raise_exception=False))}
@ -36,11 +42,17 @@ class StripeSettings(Document):
make_get_request(url="https://api.stripe.com/v1/charges", headers=header)
except Exception:
frappe.throw(_("Seems Publishable Key or Secret Key is wrong !!!"))
def validate_transaction_currency(self, currency):
if currency not in self.supported_currencies:
frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency))
def validate_minimum_transaction_amount(self, currency, amount):
if currency in self.currency_wise_minimum_charge_amount:
if flt(amount) < self.currency_wise_minimum_charge_amount.get(currency, 0.0):
frappe.throw(_("For currency {0}, the minimum transaction amount should be {1}").format(currency,
self.currency_wise_minimum_charge_amount.get(currency, 0.0)))
def get_payment_url(self, **kwargs):
return get_url("./integrations/stripe_checkout?{0}".format(urlencode(kwargs)))

View file

@ -47,7 +47,7 @@ def set_new_name(doc):
if autoname.startswith("naming_series:"):
set_name_by_naming_series(doc)
elif "#" in autoname:
doc.name = make_autoname(autoname)
doc.name = make_autoname(autoname, doc=doc)
elif autoname.lower()=='prompt':
# set from __newname in save.py
if not doc.name:

View file

@ -33,12 +33,19 @@ frappe.dom = {
document.getElementsByTagName('head')[0].appendChild(el);
},
remove_script_and_style: function(txt) {
const evil_tags = ["script", "style", "noscript", "title", "meta", "base", "head"];
const regex = new RegExp(evil_tags.map(tag => `<${tag}>.*<\\/${tag}>`).join('|'));
if (!regex.test(txt)) {
// no evil tags found, skip the DOM method entirely!
return txt;
}
var div = document.createElement('div');
div.innerHTML = txt;
var found = false;
["script", "style", "noscript", "title", "meta", "base", "head"].forEach(function(e, i) {
evil_tags.forEach(function(e) {
var elements = div.getElementsByTagName(e);
var i = elements.length;
i = elements.length;
while (i--) {
found = true;
elements[i].parentNode.removeChild(elements[i]);

View file

@ -19,6 +19,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
var save = function () {
remove_empty_rows();
$(frm.wrapper).addClass('validated-form');
if (check_mandatory()) {
_call({
@ -127,9 +128,8 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
if (df.reqd && !frappe.model.has_value(doc.doctype, doc.name, df.fieldname)) {
has_errors = true;
error_fields[error_fields.length] = __(df.label);
// scroll to field
if (!me.scroll_set) {
if (!frm.scroll_set) {
scroll_to(doc.parentfield || df.fieldname);
}
@ -141,6 +141,7 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
}
});
if (error_fields.length) {
if (doc.parenttype) {
var message = __('Mandatory fields required in table {0}, Row {1}',

View file

@ -225,7 +225,7 @@ frappe.views.BaseList = class BaseList {
}
setup_result_area() {
this.$result = $(`<div class="result">`).hide();
this.$result = $(`<div class="result">`);
this.$frappe_list.append(this.$result);
}
@ -531,19 +531,6 @@ class FilterArea {
}
make_standard_filters() {
$(
`<div class="flex justify-center align-center">
<span class="octicon octicon-search text-muted small"></span>
</div>`
)
.css({
height: '30px',
width: '20px',
marginRight: '-2px',
marginLeft: '10px'
})
.prependTo(this.standard_filters_wrapper);
let fields = [
{
fieldtype: 'Data',
@ -602,6 +589,21 @@ class FilterArea {
}
fields.map(df => this.list_view.page.add_field(df));
// search icon in name filter
$('<span class="octicon octicon-search text-muted small"></span>')
.appendTo(this.list_view.page.fields_dict.name.$wrapper)
.css({
'position': 'absolute',
'z-index': '1',
'right': '7px',
'top': '9px',
'font-size': '90%'
});
this.list_view.page.fields_dict.name.$wrapper
.find('.form-control')
.css('padding-right', '2em');
}
get_standard_filters() {

View file

@ -145,6 +145,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
setup_view() {
this.setup_columns();
this.render_header();
this.render_skeleton();
this.setup_events();
this.settings.onload && this.settings.onload(this);
}
@ -233,10 +235,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
</div>`;
}
freeze(show) {
this.$result.find('.list-header-meta').html(__('Refreshing') + '...');
this.$result.find('.checkbox-actions').toggle(show);
this.$result.find('.list-header-subject').toggle(!show);
freeze() {
this.$result.find('.list-count').html(`<span>${__('Refreshing')}...</span>`);
}
get_args() {
@ -264,6 +264,18 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
}
render_header() {
if (this.$result.find('.list-row-head').length === 0) {
// append header once
this.$result.prepend(this.get_header_html());
}
}
render_skeleton() {
const $row = this.get_list_row_html_skeleton('<div><input type="checkbox" /></div>');
this.$result.append($row.repeat(3));
}
before_render() {
this.settings.before_render && this.settings.before_render();
frappe.model.user_settings.save(this.doctype, 'last_view', this.view_name);
@ -274,12 +286,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
}
render() {
this.$result.find('.list-row-container').remove();
if (this.data.length > 0) {
this.$result.find('.list-row-container').remove();
if (this.$result.find('.list-row-head').length === 0) {
// append header once
this.$result.prepend(this.get_header_html());
}
// append rows
this.$result.append(
this.data.map(doc => this.get_list_row_html(doc)).join('')

View file

@ -19,7 +19,12 @@ $.extend(frappe.model, {
for(var i=0, l=r.docs.length; i<l; i++) {
var d = r.docs[i];
frappe.model.add_to_locals(d);
if (locals[d.doctype] && locals[d.doctype][d.name]) {
// update values
frappe.model.update_in_locals(d);
} else {
frappe.model.add_to_locals(d);
}
d.__last_sync_on = new Date();
@ -27,10 +32,6 @@ $.extend(frappe.model, {
frappe.meta.sync(d);
}
if(cur_frm && cur_frm.doctype==d.doctype && cur_frm.docname==d.name) {
cur_frm.doc = d;
}
if(d.localname) {
frappe.model.new_names[d.localname] = d.name;
$(document).trigger('rename', [d.doctype, d.localname, d.name]);
@ -96,6 +97,54 @@ $.extend(frappe.model, {
}
}
}
},
update_in_locals: function(doc) {
// update values in the existing local doc instead of replacing
let local_doc = locals[doc.doctype][doc.name];
for (let fieldname in doc) {
let df = frappe.meta.get_field(doc.doctype, fieldname);
if (df && df.fieldtype === 'Table') {
// table
if (!(doc[fieldname] instanceof Array)) {
doc[fieldname] = [];
}
if (!(local_doc[fieldname] instanceof Array)) {
local_doc[fieldname] = [];
}
// child table, override each row and append new rows if required
for (let i=0; i < doc[fieldname].length; i++ ) {
let d = doc[fieldname][i];
if (local_doc[fieldname][i]) {
// row exists, just copy the values
Object.assign(local_doc[fieldname][i], d);
} else {
local_doc[fieldname].push(d);
if (!d.parent) d.parent = doc.name;
frappe.model.add_to_locals(d);
}
}
// remove extra rows
if (local_doc[fieldname].length > doc[fieldname].length) {
for (let i = doc[fieldname].length; i < local_doc[fieldname].length; i++) {
// clear from local
let d = local_doc[fieldname][i];
if (locals[d.doctype] && locals[d.doctype][d.name]) {
delete locals[d.doctype][d.name];
}
}
local_doc[fieldname].length = doc[fieldname].length;
}
} else {
// literal
local_doc[fieldname] = doc[fieldname];
}
}
}
});

View file

@ -313,26 +313,38 @@ frappe.upload = {
if (opts.no_socketio || frappe.flags.no_socketio || file_not_big_enough) {
upload_with_filedata();
return;
} else {
args.file_size = fileobj.size;
frappe.call({
method: 'frappe.utils.file_manager.validate_filename',
args: {"filename": args.filename},
callback: function(r) {
args.filename = r.message;
upload_through_socketio();
}
});
}
frappe.socketio.uploader.start({
file: fileobj,
filename: args.filename,
is_private: args.is_private,
fallback: () => {
// if fails, use old filereader
upload_with_filedata();
},
callback: (data) => {
args.file_url = data.file_url;
frappe.upload._upload_file(fileobj, args, opts);
},
on_progress: (percent_complete) => {
let increment = (flt(percent_complete) / frappe.upload.total_files);
frappe.show_progress(__('Uploading'),
start_complete + increment);
}
});
var upload_through_socketio = function() {
frappe.socketio.uploader.start({
file: fileobj,
filename: args.filename,
is_private: args.is_private,
fallback: () => {
// if fails, use old filereader
upload_with_filedata();
},
callback: (data) => {
args.file_url = data.file_url;
frappe.upload._upload_file(fileobj, args, opts);
},
on_progress: (percent_complete) => {
let increment = (flt(percent_complete) / frappe.upload.total_files);
frappe.show_progress(__('Uploading'),
start_complete + increment);
}
});
}
},
upload_to_server: function(file, args, opts) {

View file

@ -555,10 +555,10 @@ frappe.views.CommunicationComposer = Class.extend({
is_print_letterhead_checked: function() {
if (this.frm && $(this.frm.wrapper).find('.form-print-wrapper').is(':visible')){
return $(this.frm.wrapper).find('.print-letterhead').prop('checked');
return $(this.frm.wrapper).find('.print-letterhead').prop('checked') ? 1 : 0;
} else {
return (frappe.model.get_doc(":Print Settings", "Print Settings") ||
{ with_letterhead: 1 }).with_letterhead ? true : false;
{ with_letterhead: 1 }).with_letterhead ? 1 : 0;
}
},

View file

@ -3,6 +3,10 @@
<h2>{{ __(title) }}</h2>
<hr>
{% endif %}
{% if subtitle %}
{{ subtitle }}
<hr>
{% endif %}
<table class="table table-bordered">
<!-- heading -->
<thead>
@ -24,7 +28,7 @@
{% for col in columns %}
{% if col.name && col._id !== "_check" %}
{% var value = col.fieldname ? row[col.fieldname] : row[col.field]; %}
{% var value = col.fieldname ? row[col.fieldname] : row[col.id]; %}
<td>
{{ col.formatter

View file

@ -2,10 +2,10 @@
// MIT License. See license.txt
import DataTable from 'frappe-datatable';
frappe.provide("frappe.views");
frappe.provide("frappe.query_reports");
frappe.provide('frappe.views');
frappe.provide('frappe.query_reports');
frappe.standard_pages["query-report"] = function() {
frappe.standard_pages['query-report'] = function() {
var wrapper = frappe.container.add_page('query-report');
frappe.ui.make_app_page({
@ -18,7 +18,7 @@ frappe.standard_pages["query-report"] = function() {
parent: wrapper,
});
$(wrapper).bind("show", function() {
$(wrapper).bind('show', function() {
frappe.query_report.show();
});
};
@ -86,7 +86,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
frappe.run_serially([
() => this.get_report_doc(),
() => this.get_report_settings(),
() => this.report_settings.onload && this.report_settings.onload(this),
() => this.setup_page_head(),
() => this.refresh_report()
]);
@ -98,6 +97,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
return frappe.run_serially([
() => this.setup_filters(),
() => this.set_route_filters(),
() => this.report_settings.onload && this.report_settings.onload(this),
() => this.get_user_settings(),
() => this.refresh(),
() => this.save_user_settings()
@ -208,8 +208,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
this.toggle_message(true);
const filters = this.get_filter_values(true);
return new Promise(resolve => frappe.call({
method: "frappe.desk.query_report.run",
type: "GET",
method: 'frappe.desk.query_report.run',
type: 'GET',
args: {
report_name: this.report_name,
filters: filters
@ -230,22 +230,21 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
render_report(data) {
this._data = data.result;
this._columns = this.prepare_columns(data.columns);
this.is_tree_report = this._data.some(d => 'indent' in d);
const columns = this.get_columns_for_datatable();
this.columns = this.prepare_columns(data.columns);
this.data = this.prepare_data(data.result);
this.tree_report = this.data.some(d => 'indent' in d);
const columns = this.get_visible_columns();
if (this.datatable) {
this.datatable.refresh(this._data, columns);
this.datatable.refresh(this.data, columns);
return;
}
this.datatable = new DataTable(this.$report[0], {
columns: columns,
data: this._data,
data: this.data,
inlineFilters: true,
treeView: this.is_tree_report,
treeView: this.tree_report,
layout: 'fixed',
events: {
onRemoveColumn: () => this.save_user_settings(),
@ -280,7 +279,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
save_user_settings(clear_settings = false) {
if (clear_settings) {
return frappe.model.user_settings.remove(this.report_name, 'column_order');
return frappe.model.user_settings.save(this.report_name, 'column_order', []);
}
if (!this.datatable) return;
const column_order = this.datatable.datamanager.getColumns(true).map(col => col.id);
@ -298,48 +297,58 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
[fieldtype, options] = fieldtype.split('/');
}
return {
column = {
label,
fieldname: label,
fieldtype,
width,
options
};
} else {
column = {
label: column,
fieldname: column,
fieldtype: 'Data'
};
}
return {
label: column,
fieldname: column,
fieldtype: 'Data'
};
}
return column;
return Object.assign(column, {
id: column.fieldname,
name: column.label,
width: parseInt(column.width) || null,
editable: false,
format: (value, row, column, data) =>
frappe.format(value || '', column,
{for_print: false, always_show_decimals: true}, data)
});
});
}
get_columns_for_datatable() {
const columns = this._columns.map(df => {
return {
id: df.fieldname,
name: df.label,
width: df.width || null,
editable: false,
format: (value, row, column, data) =>
frappe.format(value || '', df,
{for_print: false, always_show_decimals: true}, data)
};
prepare_data(data) {
return data.map(row => {
let row_obj = {};
if (Array.isArray(row)) {
this.columns.forEach((column, i) => {
row_obj[column.id] = row[i] || null;
});
return row_obj;
}
return row;
});
}
return columns;
get_visible_columns() {
// return columns according to user_settings
// if (this.user_settings.column_order && this.user_settings.column_order.length > 0) {
// return this.user_settings.column_order
// .map(id => columns.find(col => col.id === id))
// .filter(Boolean);
// } else {
// return columns;
// }
if (this.user_settings.column_order && this.user_settings.column_order.length > 0) {
return this.user_settings.column_order
.map(id => this.columns.find(col => col.id === id))
.filter(Boolean);
} else {
return this.columns;
}
}
get_filter_values(raise) {
@ -348,8 +357,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
if (raise && missing_mandatory.length > 0) {
// this.chart_area.hide();
// this.wrapper.find(".waiting-area").empty().toggle(false);
// this.$no_result.html(__("Please set filters")).show();
// this.wrapper.find('.waiting-area').empty().toggle(false);
// this.$no_result.html(__('Please set filters')).show();
if (raise) {
frappe.throw(__('Filter missing: {0}', [missing_mandatory.map(f => f.df.label).join(', ')]));
}
@ -381,14 +390,17 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
print_report(print_settings) {
const columns = this.get_columns_for_print();
const custom_format = this.report_settings.html_format || null;
const filters_html = this.get_filters_html_for_print();
frappe.render_grid({
template: this.report_settings.html_format || null,
template: custom_format,
title: __(this.report_name),
subtitle: filters_html,
print_settings: print_settings,
filters: this.get_filter_values(),
data: this.get_data_for_print(),
columns: columns,
data: custom_format ? this.data : this.get_data_for_print(),
columns: custom_format ? this.columns: this.get_visible_columns(),
report: this
});
}
@ -396,49 +408,48 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
pdf_report(print_settings) {
const base_url = frappe.urllib.get_base_url();
const print_css = frappe.boot.print_css;
const landscape = print_settings.orientation == "Landscape";
const columns = this.columns;
const landscape = print_settings.orientation == 'Landscape';
let html;
if (this.report_settings.html_format) {
const content = frappe.render(this.report_settings.html_format, {
data: this.get_data_for_print(),
filters: this.get_filter_values(),
report: this,
});
const custom_format = this.report_settings.html_format || null;
const columns = custom_format ? this.columns : this.get_visible_columns();
const data = custom_format ? this.data : this.get_data_for_print();
const applied_filters = this.get_filter_values();
//Render Report in HTML
html = frappe.render_template("print_template", {
title:__(this.report_name),
content: content,
base_url: base_url,
print_css: print_css,
print_settings: print_settings,
landscape: landscape,
columns: columns
});
} else {
const content = frappe.render_template("print_grid", {
title: __(this.report_name),
data: this.get_data_for_print(),
columns: columns
});
const filters_html = this.get_filters_html_for_print();
const content = frappe.render_template(custom_format || 'print_grid', {
title: __(this.report_name),
subtitle: filters_html,
filters: applied_filters,
data: data,
columns: columns,
report: this
});
//Render Report in HTML
html = frappe.render_template("print_template", {
content: content,
title: __(this.report_name),
base_url: base_url,
print_css: print_css,
print_settings: print_settings,
landscape: landscape,
columns: columns
});
}
// Render Report in HTML
const html = frappe.render_template('print_template', {
title: __(this.report_name),
content: content,
base_url: base_url,
print_css: print_css,
print_settings: print_settings,
landscape: landscape,
columns: columns
});
frappe.render_pdf(html, print_settings);
}
get_filters_html_for_print() {
const applied_filters = this.get_filter_values();
return Object.keys(applied_filters)
.map(filter_name => {
const label = frappe.query_report_filters_by_name[filter_name].df.label;
const value = applied_filters[filter_name];
return `<h6>${__(label)}: ${value}</h6>`;
})
.join('');
}
export_report() {
if (this.export_dialog) {
this.export_dialog.clear();
@ -455,8 +466,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
reqd: 1
}, ({ file_format }) => {
if (file_format === 'CSV') {
const column_row = this._columns.map(col => col.label);
const data = this.get_data_for_print();
const column_row = this.columns.map(col => col.label);
const data = this.get_data_for_csv();
const out = [column_row].concat(data);
frappe.tools.downloadify(out, null, this.report_name);
@ -473,17 +484,22 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
open_url_post(frappe.request.url, args);
}
}, __("Export Report: "+ this.report_name), __("Download"));
}, __('Export Report: '+ this.report_name), __('Download'));
}
get_data_for_print() {
get_data_for_csv() {
const indices = this.datatable.datamanager.getFilteredRowIndices();
const out = indices.map(i => this.datatable.datamanager.getRow(i).map(c => c.content));
return out.map(row => row.slice(1));
}
get_data_for_print() {
const indices = this.datatable.datamanager.getFilteredRowIndices();
return indices.map(i => this.data[i]);
}
get_columns_for_print() {
return this._columns || [];
return this.columns || [];
}
get_menu_items() {
@ -495,7 +511,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
},
{
label: __('Edit'),
action: () => frappe.set_route("Form", "Report", this.report_name),
action: () => frappe.set_route('Form', 'Report', this.report_name),
condition: () => frappe.user.is_report_manager(),
standard: true
},
@ -536,7 +552,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
{
label: __('User Permissions'),
action: () => frappe.set_route('List', 'User Permission', {
doctype: "Report",
doctype: 'Report',
name: this.report_name
}),
condition: () => frappe.model.can_set_user_permissions('Report'),
@ -557,7 +573,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
setup_page_head() {
super.setup_page_head();
this.page.set_title_sub(`<label class="label label-warning text-color">${__('Beta')}</label>`);
this.page.set_title_sub(`<label class='label label-warning text-color'>${__('Beta')}</label>`);
}
setup_report_wrapper() {
@ -568,7 +584,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
message_div(message) {
return `<div class="flex justify-center align-center text-muted" style="height: 50vh;">
return `<div class='flex justify-center align-center text-muted' style='height: 50vh;'>
<div>${message}</div>
</div>`;
}
@ -596,12 +612,4 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
get get_values() {
return this.get_filter_values;
}
get data() {
return this._data;
}
get columns() {
return this._columns;
}
};
};

View file

@ -197,12 +197,14 @@ _f.Frm.prototype.watch_model_updates = function() {
frappe.model.on(me.doctype, "*", function(fieldname, value, doc) {
// set input
if(doc.name===me.docname) {
if ((value==='' || value===null) && !doc[value]) {
if ((value==='' || value===null) && !doc[fieldname]) {
// both the incoming and outgoing values are falsy
// the texteditor, summernote, changes nulls to empty strings on render,
// so ignore those changes
} else {
me.dirty();
if (value != doc[fieldname]) {
me.dirty();
}
}
me.fields_dict[fieldname]
&& me.fields_dict[fieldname].refresh(fieldname);
@ -764,53 +766,60 @@ _f.Frm.prototype._save = function(save_action, callback, btn, on_error, resolve)
_f.Frm.prototype.savesubmit = function(btn, callback, on_error) {
var me = this;
let handle_fail = () => {
$(btn).prop('disabled', false);
if (on_error) {
on_error();
}
}
return new Promise(resolve => {
this.validate_form_action("Submit");
frappe.confirm(__("Permanently Submit {0}?", [this.docname]), function() {
frappe.validated = true;
me.script_manager.trigger("before_submit").then(function() {
if(!frappe.validated) {
if(on_error) {
on_error();
}
handle_fail();
return;
}
me.save('Submit', function(r) {
if(r.exc) {
if (on_error) {
on_error();
}
handle_fail();
} else {
frappe.utils.play_sound("submit");
callback && callback();
me.script_manager.trigger("on_submit")
.then(() => resolve(me));
}
}, btn, on_error, resolve);
}, btn, () => handle_fail(), resolve);
});
}, on_error);
}, () => handle_fail() );
});
};
_f.Frm.prototype.savecancel = function(btn, callback, on_error) {
var me = this;
let handle_fail = () => {
$(btn).prop('disabled', false);
if (on_error) {
on_error();
}
}
this.validate_form_action('Cancel');
frappe.confirm(__("Permanently Cancel {0}?", [this.docname]), function() {
frappe.validated = true;
me.script_manager.trigger("before_cancel").then(function() {
if(!frappe.validated) {
if(on_error) {
on_error();
}
handle_fail();
return;
}
var after_cancel = function(r) {
if(r.exc) {
if (on_error) {
on_error();
}
handle_fail();
} else {
frappe.utils.play_sound("cancel");
me.refresh();
@ -820,7 +829,7 @@ _f.Frm.prototype.savecancel = function(btn, callback, on_error) {
};
frappe.ui.form.save(me, "cancel", after_cancel, btn);
});
}, on_error);
}, () => handle_fail());
};
// delete the record

View file

@ -56,19 +56,21 @@ login.bind_events = function() {
return false;
});
$(".btn-ldap-login").on("click", function(){
var args = {};
args.cmd = "{{ ldap_settings.method }}";
args.usr = ($("#login_email").val() || "").trim();
args.pwd = $("#login_password").val();
args.device = "desktop";
if(!args.usr || !args.pwd) {
login.set_indicator("{{ _("Both login and password required") }}", 'red');
{% if ldap_settings %}
$(".btn-ldap-login").on("click", function(){
var args = {};
args.cmd = "{{ ldap_settings.method }}";
args.usr = ($("#login_email").val() || "").trim();
args.pwd = $("#login_password").val();
args.device = "desktop";
if(!args.usr || !args.pwd) {
login.set_indicator("{{ _("Both login and password required") }}", 'red');
return false;
}
login.call(args);
return false;
}
login.call(args);
return false;
});
});
{% endif %}
}

View file

@ -21,6 +21,7 @@ class TestOAuth20(unittest.TestCase):
frappe_login_key = frappe.new_doc("Social Login Key")
frappe_login_key.get_social_login_provider("Frappe", initialize=True)
frappe_login_key.base_url = "http://localhost:8000"
frappe_login_key.enable_social_login = 0
frappe_login_key.save()
def test_invalid_login(self):
@ -88,6 +89,26 @@ class TestOAuth20(unittest.TestCase):
# Check revoked token
self.assertFalse(check_valid_openid_response(bearer_token.get("access_token")))
def test_resource_owner_password_credentials_grant(self):
# Set payload
payload = "grant_type=password"
payload += "&username=test@example.com"
payload += "&password=Eastern_43A1W"
payload += "&client_id=" + self.client_id
payload += "&scope=openid%20all"
headers = {'content-type':'application/x-www-form-urlencoded'}
# Request for bearer token
token_response = requests.post( frappe.get_site_config().host_name +
"/api/method/frappe.integrations.oauth2.get_token", data=payload, headers=headers)
# Parse bearer token json
bearer_token = token_response.json()
# Check token for valid response
self.assertTrue(check_valid_openid_response(bearer_token.get("access_token")))
def test_login_using_implicit_token(self):
oauth_client = frappe.get_doc("OAuth Client", self.client_id)

View file

@ -16,6 +16,7 @@ import cgitb
import types
import datetime
import json
import six
def make_error_snapshot(exception):
if frappe.conf.disable_error_snapshot:
@ -49,7 +50,7 @@ def get_snapshot(exception, context=10):
"""
etype, evalue, etb = sys.exc_info()
if isinstance(etype, types.ClassType):
if isinstance(etype, six.class_types):
etype = etype.__name__
# creates a snapshot dict with some basic information

View file

@ -88,6 +88,7 @@ def save_url(file_url, filename, dt, dn, folder, is_private, df=None):
# return None, None
file_url = unquote(file_url)
file_size = frappe.form_dict.file_size
f = frappe.get_doc({
"doctype": "File",
@ -97,6 +98,7 @@ def save_url(file_url, filename, dt, dn, folder, is_private, df=None):
"attached_to_name": dn,
"attached_to_field": df,
"folder": folder,
"file_size": file_size,
"is_private": is_private
})
f.flags.ignore_permissions = True
@ -416,3 +418,9 @@ def get_random_filename(extn=None, content_type=None):
extn = mimetypes.guess_extension(content_type)
return random_string(7) + (extn or "")
@frappe.whitelist()
def validate_filename(filename):
hash_ = get_content_hash(filename)
fname = get_file_name(filename, hash_[-6:])
return fname

View file

@ -16,7 +16,8 @@
</div>
<input type="text" id="login_email"
class="form-control" placeholder="{{ login_name_placeholder }}"
class="form-control"
placeholder="{% if login_name_placeholder %}{{ login_name_placeholder }}{% else %}{{ _("Email address") }}{% endif %}"
required autofocus>
@ -26,7 +27,7 @@
<button class="btn btn-sm btn-primary btn-block btn-login" type="submit">
{{ _("Sign in") }}</button>
{% if ldap_settings.enabled %}
{% if ldap_settings and ldap_settings.enabled %}
<button class="btn btn-sm btn-default btn-block btn-login btn-ldap-login">
{{ _("Login with LDAP") }}</button>
{% endif %}

View file

@ -9,6 +9,9 @@
href="/assets/frappe/css/bootstrap.css">
<link type="text/css" rel="stylesheet"
href="/assets/frappe/css/font-awesome.css">
{%- if has_rtl -%}
<link type="text/css" rel="stylesheet" href="assets/css/frappe-rtl.css">
{%- endif -%}
<style>
{{ css }}
</style>

View file

@ -41,7 +41,8 @@ def get_context(context):
no_letterhead=frappe.form_dict.no_letterhead),
"css": get_print_style(frappe.form_dict.style, print_format),
"comment": frappe.session.user,
"title": doc.get(meta.title_field) if meta.title_field else doc.name
"title": doc.get(meta.title_field) if meta.title_field else doc.name,
"has_rtl": True if frappe.local.lang in ["ar", "he", "fa"] else False
}
def get_print_format_doc(print_format_name, meta):

View file

@ -4,13 +4,14 @@ from frappe.utils.global_search import web_search
from html2text import html2text
from frappe import _
from jinja2 import utils
from frappe.utils import sanitize_html
def get_context(context):
context.no_cache = 1
if frappe.form_dict.q:
frappe.form_dict.q = str(utils.escape(frappe.form_dict.q))
context.title = _('Search Results for "{0}"').format(frappe.form_dict.q)
context.update(get_search_results(frappe.form_dict.q))
query = str(utils.escape(sanitize_html(frappe.form_dict.q)))
context.title = _('Search Results for "{0}"').format(query)
context.update(get_search_results(query))
else:
context.title = _('Search')

View file

@ -63,6 +63,7 @@ io.on('connection', function (socket) {
socket.join(room);
}
});
socket.on("frappe.chat.message:typing", function (data) {
const user = data.user;
const room = get_chat_room(socket, data.room);
@ -92,9 +93,7 @@ io.on('connection', function (socket) {
socket.join(get_site_room(socket));
}
});
socket.on('disconnect', function () {
delete socket.files;
})