Merge branch 'hotfix'

This commit is contained in:
Sagar Vora 2019-02-12 16:18:43 +05:30
commit af1e7b1443
30 changed files with 563 additions and 119 deletions

View file

@ -23,7 +23,7 @@ if sys.version[0] == '2':
reload(sys)
sys.setdefaultencoding("utf-8")
__version__ = '11.1.4'
__version__ = '11.1.5'
__title__ = "Frappe Framework"
local = Local()
@ -501,6 +501,7 @@ def read_only():
retval = fn(*args, **get_newargs(fn, kwargs))
if local and hasattr(local, 'master_db'):
local.db.close()
local.db = local.master_db
return retval

View file

@ -2,9 +2,84 @@
# Copyright (c) 2017, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
from frappe.core.doctype.user_permission.user_permission import add_user_permissions
#import frappe
import frappe
import unittest
class TestUserPermission(unittest.TestCase):
pass
def test_apply_to_all(self):
''' Create User permission for User having access to all applicable Doctypes'''
user = get_user()
param = get_params(user, apply = 1)
created = add_user_permissions(param)
self.assertEquals(created, 1)
def test_for_applicable_on_update_from_apply_to_all(self):
''' Update User Permission from all to some applicable Doctypes'''
user = get_user()
param = get_params(user, applicable = ["Chat Room", "Chat Message"])
create = add_user_permissions(param)
frappe.db.commit()
removed_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
created_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
created_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message"))
self.assertIsNone(removed_apply_to_all)
self.assertIsNotNone(created_applicable_first)
self.assertIsNotNone(created_applicable_second)
self.assertEquals(create, 1)
def test_for_apply_to_all_on_update_from_applicable(self):
''' Update User Permission from some to all applicable Doctypes'''
user = get_user()
param = get_params(user, apply = 1)
created = add_user_permissions(param)
created_apply_to_all = frappe.db.exists("User Permission", get_exists_param(user))
removed_applicable_first = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Room"))
removed_applicable_second = frappe.db.exists("User Permission", get_exists_param(user, applicable = "Chat Message"))
self.assertIsNotNone(created_apply_to_all)
self.assertIsNone(removed_applicable_first)
self.assertIsNone(removed_applicable_second)
self.assertEquals(created, 1)
def get_user():
if frappe.db.exists('User', 'test_bulk_creation_update@example.com'):
return frappe.get_doc('User', 'test_bulk_creation_update@example.com')
else:
user = frappe.new_doc('User')
user.email = 'test_bulk_creation_update@example.com'
user.first_name = 'Test_Bulk_Creation'
user.add_roles("System Manager")
return user
def get_params(user, apply = None , applicable = None):
''' Return param to insert '''
param = {
"user": user.name,
"doctype":"User",
"docname":user.name
}
if apply:
param.update({"apply_to_all_doctypes": 1})
param.update({"applicable_doctypes": []})
if applicable:
param.update({"apply_to_all_doctypes": 0})
param.update({"applicable_doctypes": applicable})
return param
def get_exists_param(user, applicable = None):
''' param to check existing Document '''
param = {
"user": user.name,
"allow": "User",
"for_value": user.name,
}
if applicable:
param.update({"applicable_for": applicable})
else:
param.update({"apply_to_all_doctypes": 1})
return param

View file

@ -42,6 +42,9 @@ def get_user_permissions(user=None):
if not user:
user = frappe.session.user
if user == "Administrator":
return {}
cached_user_permissions = frappe.cache().hget("user_permissions", user)
if cached_user_permissions is not None:
@ -112,12 +115,100 @@ def get_permitted_documents(doctype):
return [d.get('doc') for d in get_user_permissions().get(doctype, []) \
if d.get('doc')]
@frappe.whitelist()
def check_applicable_doc_perm(user, doctype, docname):
frappe.only_for('System Manager')
applicable = []
doc_exists = frappe.get_all('User Permission',
fields=['name'],
filters={"user": user,
"allow": doctype,
"for_value": docname,
"apply_to_all_doctypes":1,
}, limit=1)
if doc_exists:
applicable = get_linked_doctypes(doctype).keys()
else:
data = frappe.get_all('User Permission',
fields=['applicable_for'],
filters={"user": user,
"allow": doctype,
"for_value":docname,
})
for d in data:
applicable.append(d.applicable_for)
return applicable
@frappe.whitelist()
def clear_user_permissions(user, for_doctype):
frappe.only_for('System Manager')
total = frappe.db.count('User Permission', filters = dict(user=user, allow=for_doctype))
if total:
frappe.db.sql('DELETE FROM `tabUser Permission` WHERE user=%s AND allow=%s', (user, for_doctype))
frappe.clear_cache()
return total
@frappe.whitelist()
def add_user_permissions(data):
''' Add and update the user permissions '''
frappe.only_for('System Manager')
if isinstance(data, frappe.string_types):
data = json.loads(data)
data = frappe._dict(data)
d = check_applicable_doc_perm(data.user, data.doctype, data.docname)
exists = frappe.db.exists("User Permission", {"user": data.user, "allow": data.doctype, "for_value": data.docname, "apply_to_all_doctypes": 1})
if data.apply_to_all_doctypes == 1 and not exists:
remove_applicable(d, data.user, data.doctype, data.docname)
insert_user_perm(data.user, data.doctype, data.docname, apply_to_all = 1)
return 1
else:
remove_apply_to_all(data.user, data.doctype, data.docname)
update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname)
for applicable in data.applicable_doctypes :
if applicable not in d:
insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable)
elif exists:
insert_user_perm(data.user, data.doctype, data.docname, applicable = applicable)
return 1
return 0
def insert_user_perm(user, doctype, docname, apply_to_all=None, applicable=None):
user_perm = frappe.new_doc("User Permission")
user_perm.user = user
user_perm.allow = doctype
user_perm.for_value = docname
if applicable:
user_perm.applicable_for = applicable
user_perm.apply_to_all_doctypes = 0
else:
user_perm.apply_to_all_doctypes = 1
user_perm.insert()
def remove_applicable(d, user, doctype, docname):
for applicable_for in d:
frappe.db.sql("""DELETE FROM `tabUser Permission`
WHERE `user`=%s
AND `applicable_for`=%s
AND `allow`=%s
AND `for_value`=%s
""", (user, applicable_for, doctype, docname))
def remove_apply_to_all(user, doctype, docname):
frappe.db.sql("""DELETE from `tabUser Permission`
WHERE `user`=%s
AND `apply_to_all_doctypes`=1
AND `allow`=%s
AND `for_value`=%s
""",(user, doctype, docname))
def update_applicable(already_applied, to_apply, user, doctype, docname):
for applied in already_applied:
if applied not in to_apply:
frappe.db.sql("""DELETE FROM `tabUser Permission`
WHERE `user`=%s
AND `applicable_for`=%s
AND `allow`=%s
AND `for_value`=%s
""",(user, applied, doctype, docname))

View file

@ -1,22 +1,123 @@
frappe.listview_settings['User Permission'] = {
onload: function(list_view) {
list_view.page.add_menu_item(__("Clear User Permissions"), () => {
var me = this;
list_view.page.add_inner_button( __("Add / Update"), function() {
let dialog =new frappe.ui.Dialog({
title : __('Add User Permissions'),
fields: [
{
fieldname: 'user',
label: __('For User'),
fieldtype: 'Link',
options: 'User',
reqd: 1,
onchange: function() {
dialog.fields_dict.doctype.set_input(undefined);
dialog.fields_dict.docname.set_input(undefined);
dialog.set_df_property("docname", "hidden", 1);
dialog.set_df_property("apply_to_all_doctypes", "hidden", 1);
dialog.set_df_property("applicable_doctypes", "hidden", 1);
}
},
{
fieldname: 'doctype',
label: __('Document Type'),
fieldtype: 'Link',
options: 'DocType',
reqd: 1,
onchange: function() {
me.on_doctype_change(dialog);
}
},
{
fieldname: 'docname',
label: __('Document Name'),
fieldtype: 'Dynamic Link',
options: 'doctype',
hidden: 1,
onchange: function() {
let field = dialog.fields_dict["docname"];
if(field.value != field.last_value) {
if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
me.get_applicable_doctype(dialog).then(applicable => {
me.get_multi_select_options(dialog, applicable).then(options => {
me.applicable_options = options;
me.on_docname_change(dialog, options, applicable);
if(options.length > 5){
dialog.fields_dict.applicable_doctypes.setup_select_all();
}
});
});
}
}
}
},
{
fieldname: 'apply_to_all_doctypes',
label: __('Apply to all Documents Types'),
fieldtype: 'Check',
checked: 1,
hidden: 1,
onchange: function() {
if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
me.on_apply_to_all_doctypes_change(dialog, me.applicable_options);
if(me.applicable_options.length > 5){
dialog.fields_dict.applicable_doctypes.setup_select_all();
}
}
}
},
{
label: __("Applicable Document Types"),
fieldname: "applicable_doctypes",
fieldtype: "MultiCheck",
options: [],
columns: 2,
hidden: 1
},
],
primary_action: (data) => {
data = me.validate(dialog, data);
frappe.call({
async: false,
method: "frappe.core.doctype.user_permission.user_permission.add_user_permissions",
args: {
data : data
},
callback: function(r) {
if(r.message === 1) {
frappe.show_alert({message:__("User Permissions created sucessfully"), indicator:'blue'});
} else {
frappe.show_alert({message:__("Nothing to update"), indicator:'red'});
}
}
});
dialog.hide();
list_view.refresh();
},
primary_action_label: __('Submit')
});
dialog.show();
});
list_view.page.add_inner_button( __("Bulk Delete"), function() {
const dialog = new frappe.ui.Dialog({
title: __('Clear User Permissions'),
fields: [
{
'fieldname': 'user',
'label': __('For User'),
'fieldtype': 'Link',
'options': 'User',
'reqd': 1
fieldname: 'user',
label: __('For User'),
fieldtype: 'Link',
options: 'User',
reqd: 1
},
{
'fieldname': 'for_doctype',
'label': __('For Document Type'),
'fieldtype': 'Link',
'options': 'DocType',
'reqd': 1
fieldname: 'for_doctype',
label: __('For Document Type'),
fieldtype: 'Link',
options: 'DocType',
reqd: 1
},
],
primary_action: (data) => {
@ -31,6 +132,8 @@ frappe.listview_settings['User Permission'] = {
let message = '';
if (data === 0) {
message = __('No records deleted');
} else if(data === 1) {
message = __('{0} record deleted', [data]);
} else {
message = __('{0} records deleted', [data]);
}
@ -43,10 +146,95 @@ frappe.listview_settings['User Permission'] = {
});
},
primary_action_label: __('Clear')
primary_action_label: __('Delete')
});
dialog.show();
});
},
validate: function(dialog, data) {
if(dialog.fields_dict.applicable_doctypes.get_unchecked_options().length == 0) {
data.apply_to_all_doctypes = 1;
data.applicable_doctypes = [];
return data;
}
if(data.apply_to_all_doctypes == 0 && !("applicable_doctypes" in data)) {
frappe.throw("Please select applicable Doctypes");
}
return data;
},
get_applicable_doctype: function(dialog) {
return new Promise(resolve => {
frappe.call({
method: 'frappe.core.doctype.user_permission.user_permission.check_applicable_doc_perm',
async: false,
args:{
user: dialog.fields_dict.user.value,
doctype: dialog.fields_dict.doctype.value,
docname: dialog.fields_dict.docname.value
}
}).then(r => {
resolve(r.message);
});
});
},
get_multi_select_options: function(dialog, applicable){
return new Promise(resolve => {
frappe.call({
method: 'frappe.desk.form.linked_with.get_linked_doctypes',
async: false,
args:{
user: dialog.fields_dict.user.value,
doctype: dialog.fields_dict.doctype.value,
docname: dialog.fields_dict.docname.value
}
}).then(r => {
var options = [];
for(var d in r.message){
var checked = ($.inArray(d, applicable) != -1) ? 1 : 0;
options.push({ "label":d, "value": d , "checked": checked});
}
resolve(options);
});
});
},
on_doctype_change: function(dialog) {
dialog.set_df_property("docname", "hidden", 0);
dialog.set_df_property("docname", "reqd", 1);
dialog.set_df_property("apply_to_all_doctypes", "hidden", 0);
dialog.set_value("apply_to_all_doctypes","checked",1);
},
on_docname_change: function(dialog, options, applicable) {
if(applicable.length != 0 ) {
dialog.set_primary_action("Update");
dialog.set_title("Update User Permissions");
dialog.set_df_property("applicable_doctypes", "options", options);
if(dialog.fields_dict.applicable_doctypes.get_checked_options().length == options.length) {
dialog.set_df_property("applicable_doctypes", "hidden", 1);
} else {
dialog.set_df_property("applicable_doctypes", "hidden", 0);
dialog.set_df_property("apply_to_all_doctypes", "checked", 0);
}
} else {
dialog.set_primary_action("Submit");
dialog.set_title("Add User Permissions");
dialog.set_df_property("applicable_doctypes", "options", options);
dialog.set_df_property("applicable_doctypes", "hidden", 1);
}
},
on_apply_to_all_doctypes_change: function(dialog, options) {
if(dialog.fields_dict.apply_to_all_doctypes.get_value() == 0) {
dialog.set_df_property("applicable_doctypes", "hidden", 0);
dialog.set_df_property("applicable_doctypes", "options", options);
} else {
dialog.set_df_property("applicable_doctypes", "options", options);
dialog.set_df_property("applicable_doctypes", "hidden", 1);
}
}
};
};

View file

@ -249,6 +249,11 @@ def get_open_count(doctype, name, items=[]):
:param transactions: List of transactions (json/dict)
:param filters: optional filters (json/list)'''
if frappe.flags.in_migrate or frappe.flags.in_install:
return {
'count': []
}
frappe.has_permission(doc=frappe.get_doc(doctype, name), throw=True)
meta = frappe.get_meta(doctype)

View file

@ -7,7 +7,13 @@ import frappe
from frappe.model.document import Document
class EmailGroupMember(Document):
pass
def after_delete(self):
email_group = frappe.get_doc('Email Group', self.email_group)
email_group.update_total_subscribers()
def after_insert(self):
email_group = frappe.get_doc('Email Group', self.email_group)
email_group.update_total_subscribers()
def after_doctype_insert():
frappe.db.add_unique("Email Group Member", ("email_group", "email"))
frappe.db.add_unique("Email Group Member", ("email_group", "email"))

View file

@ -137,7 +137,7 @@ def unsubscribe(email, name):
return
primary_action = frappe.utils.get_url() + "/api/method/frappe.email.doctype.newsletter.newsletter.confirmed_unsubscribe"+\
"?" + get_signed_params({"email": email, "name":name})
"?" + get_signed_params({"email": email, "name":name.encode('utf-8')})
return_confirmation_page(email, name, primary_action)

View file

@ -212,8 +212,15 @@ def get_context(context):
please enable Allow Print For {0} in Print Settings""".format(status)),
title=_("Error in Notification"))
else:
return [{"print_format_attachment":1, "doctype":doc.doctype, "name": doc.name,
"print_format":self.print_format, "print_letterhead": print_settings.with_letterhead}]
return [{
"print_format_attachment": 1,
"doctype": doc.doctype,
"name": doc.name,
"print_format": self.print_format,
"print_letterhead": print_settings.with_letterhead,
"lang": frappe.db.get_value('Print Format', self.print_format, 'default_print_language')
if self.print_format else 'en'
}]
def get_template(self):

View file

@ -175,7 +175,8 @@ def get_email_queue(recipients, sender, subject, **kwargs):
if att.get('fid'):
_attachments.append(att)
elif att.get("print_format_attachment") == 1:
att['lang'] = frappe.local.lang
if not att.get('lang', None):
att['lang'] = frappe.local.lang
att['print_letterhead'] = kwargs.get('print_letterhead')
_attachments.append(att)
e.attachments = json.dumps(_attachments)

View file

@ -12,6 +12,7 @@ import frappe.defaults
from frappe.model.db_schema import type_map
import copy
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
from frappe.permissions import get_allowed_docs_for_doctype
def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False):
if doctype not in frappe.local.new_doc_templates:
@ -53,36 +54,39 @@ def set_user_and_static_default_values(doc):
for df in doc.meta.get("fields"):
if df.fieldtype in type_map:
user_default_value = get_user_default_value(df, defaults, user_permissions)
# user permissions for link options
doctype_user_permissions = user_permissions.get(df.options, [])
# Allowed records for the reference doctype (link field)
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)
else:
if df.fieldname != doc.meta.title_field:
static_default_value = get_static_default_value(df, user_permissions)
static_default_value = get_static_default_value(df, doctype_user_permissions, allowed_records)
if static_default_value is not None:
doc.set(df.fieldname, static_default_value)
def get_user_default_value(df, defaults, user_permissions):
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":
# 1 - look in user permissions only for document_type==Setup
# We don't want to include permissions of transactions to be used for defaults.
if (frappe.get_meta(df.options).document_type=="Setup"
and user_permissions_exist(df, user_permissions)
and len(user_permissions.get(df.options))==1):
return user_permissions.get(df.options)[0].get("doc")
if frappe.get_meta(df.options).document_type=="Setup" and len(allowed_records)==1:
return allowed_records[0]
# 2 - Look in user defaults
user_default = defaults.get(df.fieldname)
is_allowed_user_default = user_default and (not user_permissions_exist(df, user_permissions)
or (user_default in user_permissions.get(df.options, [])))
is_allowed_user_default = user_default and (not user_permissions_exist(df, doctype_user_permissions)
or user_default in allowed_records)
# is this user default also allowed as per user permissions?
if is_allowed_user_default:
return user_default
def get_static_default_value(df, user_permissions):
def get_static_default_value(df, doctype_user_permissions, allowed_records):
# 3 - look in default of docfield
if df.get("default"):
if df.default == "__user":
@ -93,8 +97,8 @@ def get_static_default_value(df, user_permissions):
elif not df.default.startswith(":"):
# a simple default value
is_allowed_default_value = (not user_permissions_exist(df, user_permissions)
or (df.default in user_permissions.get(df.options, [])))
is_allowed_default_value = (not user_permissions_exist(df, doctype_user_permissions)
or (df.default in allowed_records))
if df.fieldtype!="Link" or df.options=="User" or is_allowed_default_value:
return df.default
@ -126,10 +130,10 @@ def set_dynamic_default_values(doc, parent_doc, parentfield):
if parentfield:
doc["parentfield"] = parentfield
def user_permissions_exist(df, user_permissions):
def user_permissions_exist(df, doctype_user_permissions):
return (df.fieldtype=="Link"
and not getattr(df, "ignore_user_permissions", False)
and df.options in (user_permissions or []))
and doctype_user_permissions)
def get_default_based_on_another_field(df, user_permissions, parent_doc):
# default value based on another document
@ -139,7 +143,7 @@ def get_default_based_on_another_field(df, user_permissions, parent_doc):
ref_fieldname = ref_doctype.lower().replace(" ", "_")
reference_name = parent_doc.get(ref_fieldname) if parent_doc else frappe.db.get_default(ref_fieldname)
default_value = frappe.db.get_value(ref_doctype, reference_name, df.fieldname)
is_allowed_default_value = (not user_permissions_exist(df, user_permissions) or
is_allowed_default_value = (not user_permissions_exist(df, user_permissions.get(df.options)) or
(default_value in get_allowed_docs_for_doctype(user_permissions[df.options], df.parent)))
# is this allowed as per user permissions

View file

@ -166,10 +166,10 @@ class Document(BaseDocument):
self.latest = frappe.get_doc(self.doctype, self.name)
return self.latest
def check_permission(self, permtype='read', permlabel=None):
def check_permission(self, permtype='read', permlevel=None):
"""Raise `frappe.PermissionError` if not permitted"""
if not self.has_permission(permtype):
self.raise_no_permission_to(permlabel or permtype)
self.raise_no_permission_to(permlevel or permtype)
def has_permission(self, permtype="read", verbose=False):
"""Call `frappe.has_permission` if `self.flags.ignore_permissions`
@ -989,7 +989,7 @@ class Document(BaseDocument):
frappe.db.commit()
def db_get(self, fieldname):
'''get database vale for this fieldname'''
'''get database value for this fieldname'''
return frappe.db.get_value(self.doctype, self.name, fieldname)
def check_no_back_links_exist(self):

View file

@ -5,7 +5,7 @@ import pytz
from frappe import _
from frappe.auth import LoginManager
from oauthlib.oauth2.rfc6749.tokens import BearerToken
from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant, OpenIDConnectAuthCode
from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant
from oauthlib.oauth2 import RequestValidator
from oauthlib.oauth2.rfc6749.endpoints.authorization import AuthorizationEndpoint
from oauthlib.oauth2.rfc6749.endpoints.token import TokenEndpoint
@ -40,19 +40,12 @@ class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoin
implicit_grant = ImplicitGrant(request_validator)
auth_grant = AuthorizationCodeGrant(request_validator)
refresh_grant = RefreshTokenGrant(request_validator)
openid_connect_auth = OpenIDConnectAuthCode(request_validator)
resource_owner_password_credentials_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
bearer = BearerToken(request_validator, token_generator,
token_expires_in, refresh_token_generator)
AuthorizationEndpoint.__init__(self, default_response_type='code',
response_types={
'code': auth_grant,
'code+token': openid_connect_auth,
'code+id_token': openid_connect_auth,
'code+token+id_token': openid_connect_auth,
'code token': openid_connect_auth,
'code id_token': openid_connect_auth,
'code token id_token': openid_connect_auth,
'token': implicit_grant
},
default_token_type=bearer)

View file

@ -24,8 +24,10 @@ def print_has_permission_check_logs(func):
def inner(*args, **kwargs):
frappe.flags['has_permission_check_logs'] = []
result = func(*args, **kwargs)
self_perm_check = True if not kwargs.get('user') else kwargs.get('user') == frappe.session.user
# print only if access denied
if not result:
# and if user is checking his own permission
if not result and self_perm_check:
msgprint(('<br>').join(frappe.flags['has_permission_check_logs']))
frappe.flags.pop('has_permission_check_logs', None)
return result

View file

@ -220,6 +220,16 @@ frappe.dom = {
};
reader.readAsDataURL(file_obj);
});
},
pixel_to_inches(pixels) {
const div = $('<div id="dpi" style="height: 1in; width: 1in; left: 100%; position: fixed; top: 100%;"></div>');
div.appendTo(document.body);
const dpi_x = document.getElementById('dpi').offsetWidth;
const inches = pixels / dpi_x;
div.remove();
return inches;
}
}

View file

@ -86,7 +86,9 @@ frappe.ui.form.ControlMultiCheck = frappe.ui.form.Control.extend({
this.selected_options.push(option_name);
} else {
let index = this.selected_options.indexOf(option_name);
this.selected_options.splice(index, 1);
if(index > -1) {
this.selected_options.splice(index, 1);
}
}
this.df.on_change && this.df.on_change();
});

View file

@ -136,22 +136,40 @@ frappe.ui.form.PrintPreview = Class.extend({
preview: function () {
var me = this;
this.get_print_html(function (out) {
me.wrapper.find(".print-format").html(out.html);
const $print_format = me.wrapper.find(".print-format");
$print_format.html(out.html);
me.show_footer();
me.set_style(out.style);
const print_height = $print_format.get(0).offsetHeight;
const $message = me.wrapper.find(".page-break-message");
const print_height_inches = frappe.dom.pixel_to_inches(print_height);
// if contents are large enough, indicate that it will get printed on multiple pages
// Maximum height for an A4 document is 11.69 inches
if (print_height_inches > 11.69) {
$message.text(__('This may get printed on multiple pages'));
} else {
$message.text('');
}
});
},
show_footer: function() {
// footer is hidden by default as reqd by pdf generation
// simple hack to show it in print preview
this.wrapper.find('.print-format').css({
display: 'flex',
flexDirection: 'column'
});
this.wrapper.find('.page-break').css({
'display': 'flex',
'flex-direction': 'column'
'flex-direction': 'column',
'flex': '1'
});
this.wrapper.find('#footer-html').attr('style', `
display: block !important;
order: 1;
margin-top: 20px;
margin-top: auto;
`);
},
printit: function () {

View file

@ -31,6 +31,7 @@
<div class="print-preview-wrapper">
<div class="print-preview">
<div class="print-format"></div>
</div>
</div>
<div class="page-break-message text-muted text-center text-medium margin-top"></div>
</div>
</div>

View file

@ -137,6 +137,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
);
fields.forEach(f => this._add_field(f));
this.fields.forEach(f => {
const df = frappe.meta.get_docfield(f[1], f[0]);
if (df && df.fieldtype === 'Currency' && df.options && !df.options.includes(':')) {
this._add_field(df.options);
}
});
}
patch_refresh_and_load_lib() {
@ -597,7 +604,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
args: {
doctype: this.doctype,
filters: this.get_filters_for_args(),
fields: [`count(distinct ${frappe.model.get_full_column_name('name', this.doctype)}) as total_count`],
fields: [`count(${frappe.model.get_full_column_name('name', this.doctype)}) as total_count`],
}
}).then(r => {
this.total_count = r.message.values[0][0] || current_count;

View file

@ -87,10 +87,8 @@ $.extend(frappe.model, {
var doctype = doc.doctype;
var docfields = frappe.meta.docfield_list[doctype] || [];
var updated = [];
for(var fid=0;fid<docfields.length;fid++) {
var f = docfields[fid];
if(!in_list(frappe.model.no_value_type, f.fieldtype) && doc[f.fieldname]==null) {
var v = frappe.model.get_default_value(f, doc, parent_doc);
if(v) {
@ -126,25 +124,23 @@ $.extend(frappe.model, {
get_default_value: function(df, doc, parent_doc) {
var user_default = "";
var user_permissions = frappe.defaults.get_user_permissions();
let allowed_records = [];
if(user_permissions) {
allowed_records = frappe.perm.get_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype);
}
var meta = frappe.get_meta(doc.doctype);
var has_user_permissions = (df.fieldtype==="Link"
&& user_permissions
&& !$.isEmptyObject(user_permissions)
&& df.ignore_user_permissions != 1
&& user_permissions[df.options]);
function is_doc_allowed(doctype, docname) {
return user_permissions[doctype].some(perm => {
return perm.doc === docname && (perm.applicable_for === doc.doctype || !perm.applicable_for);
});
}
&& allowed_records.length);
// don't set defaults for "User" link field using User Permissions!
if (df.fieldtype==="Link" && df.options!=="User") {
// 1 - look in user permissions for document_type=="Setup".
// We don't want to include permissions of transactions to be used for defaults.
if (df.linked_document_type==="Setup"
&& has_user_permissions && user_permissions[df.options].length===1) {
return user_permissions[df.options][0].doc;
&& has_user_permissions && allowed_records.length===1) {
return allowed_records[0];
}
if(!df.ignore_user_permissions) {
@ -165,7 +161,7 @@ $.extend(frappe.model, {
}
var is_allowed_user_default = user_default &&
(!has_user_permissions || is_doc_allowed(df.options, user_default));
(!has_user_permissions || allowed_records.includes(user_default));
// is this user default also allowed as per user permissions?
if (is_allowed_user_default) {
@ -190,7 +186,7 @@ $.extend(frappe.model, {
} else if (df["default"][0]===":") {
var boot_doc = frappe.model.get_default_from_boot_docs(df, doc, parent_doc);
var is_allowed_boot_doc = !has_user_permissions || is_doc_allowed(df.options, boot_doc);
var is_allowed_boot_doc = !has_user_permissions || allowed_records.includes(boot_doc);
if (is_allowed_boot_doc) {
return boot_doc;
@ -201,7 +197,7 @@ $.extend(frappe.model, {
}
// is this default value is also allowed as per user permissions?
var is_allowed_default = !has_user_permissions || is_doc_allowed(df.options, df.default);
var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default);
if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) {
return df["default"];
}
@ -342,5 +338,3 @@ frappe.new_doc = function (doctype, opts, init_callback) {
});
}

View file

@ -151,18 +151,10 @@ $.extend(frappe.perm, {
let rules = {};
let fields_to_check = frappe.meta.get_fields_to_check_permissions(doctype);
$.each(fields_to_check, (i, df) => {
const user_permissions_for_doctype = user_permissions[df.options];
// check if there are any user permission applicable for parent doctype
const has_user_permission = user_permissions_for_doctype ? user_permissions_for_doctype
.some(perm => !perm.applicable_for || perm.applicable_for === doctype) : false;
if (has_user_permission) {
rules[df.label] = [];
user_permissions_for_doctype.map(permission => {
if (!permission.applicable_for || permission.applicable_for === doctype) {
rules[df.label].push(permission.doc);
}
});
const user_permissions_for_doctype = user_permissions[df.options] || [];
const allowed_records = frappe.perm.get_allowed_docs_for_doctype(user_permissions_for_doctype, doctype);
if (allowed_records.length) {
rules[df.label] = allowed_records;
}
});
if (!$.isEmptyObject(rules)) {
@ -260,4 +252,10 @@ $.extend(frappe.perm, {
return status === "None" ? false : true;
},
get_allowed_docs_for_doctype: (user_permissions, doctype) => {
return (user_permissions || []).filter(perm => {
return (perm.applicable_for === doctype || !perm.applicable_for);
}).map(perm => perm.doc);
}
});

View file

@ -115,10 +115,14 @@ frappe.ui.Filter = class {
}
update_filter_tag() {
return this._filter_value_set.then(() => {
!this.$filter_tag ? this.make_tag() : this.set_filter_button_text();
this.filter_edit_area.hide();
});
if (this._filter_value_set) {
return this._filter_value_set.then(() => {
!this.$filter_tag ? this.make_tag() : this.set_filter_button_text();
this.filter_edit_area.hide();
});
} else {
return Promise.resolve();
}
}
remove() {

View file

@ -92,7 +92,7 @@ frappe.views.CommunicationComposer = Class.extend({
{label:__("Send me a copy"), fieldtype:"Check",
fieldname:"send_me_a_copy", 'default': frappe.boot.user.send_me_a_copy},
{label:__("Send Read Receipt"), fieldtype:"Check",
fieldname:"send_read_receipt", default: 1},
fieldname:"send_read_receipt"},
{label:__("Attach Document Print"), fieldtype:"Check",
fieldname:"attach_document_print"},
{label:__("Select Print Format"), fieldtype:"Select",

View file

@ -38,7 +38,8 @@ frappe.provide("frappe.views");
cards: cards,
columns: columns,
cur_list: opts.cur_list,
empty_state: false
empty_state: false,
wrapper: opts.wrapper
});
},
update_cards: function(updater, cards) {
@ -97,21 +98,33 @@ frappe.provide("frappe.views");
var doc_fields = {};
doc_fields[field.fieldname] = card_title;
doc_fields[this.board.field_name] = column_title;
this.board.filters_array.forEach(function(f) {
this.cur_list.filter_area.get().forEach(function(f) {
if (f[2] !== "=") return;
doc_fields[f[1]] = f[3];
});
$.extend(doc, doc_fields);
// add the card directly
// for better ux
const card = prepare_card(doc, state);
card._disable_click = true;
const cards = [...state.cards, card];
// remember the name which we will override later
const old_name = doc.name;
updater.set({ cards });
if (field && !quick_entry) {
return insert_doc(doc)
.then(function(r) {
var updated_doc = r.message;
var card = prepare_card(doc, state, updated_doc);
var cards = state.cards.slice();
cards.push(card);
updater.set({ cards: cards });
// update the card in place with the updated doc
const updated_doc = r.message;
const index = state.cards.findIndex(card => card.name === old_name);
const card = prepare_card(updated_doc, state);
const new_cards = state.cards.slice();
new_cards[index] = card;
updater.set({ cards: new_cards });
fluxify.doAction('update_order');
});
} else {
frappe.new_doc(this.doctype, doc);
@ -142,11 +155,22 @@ frappe.provide("frappe.views");
fluxify.doAction('update_card', updated_card);
});
},
update_order: function(updater, order) {
update_order: function(updater) {
// cache original order
const _cards = this.cards.slice();
const _columns = this.columns.slice();
const order = {};
this.wrapper.find('.kanban-column[data-column-value]')
.each(function() {
var col_name = $(this).data().columnValue;
order[col_name] = [];
$(this).find('.kanban-card-wrapper').each(function() {
var card_name = $(this).data().name;
order[col_name].push(card_name);
});
});
frappe.call({
method: method_prefix + "update_order",
args: {
@ -431,17 +455,7 @@ frappe.provide("frappe.views");
wrapper.find('.kanban-card.add-card').fadeIn(100);
wrapper.find('.kanban-cards').height('auto');
// update order
var order = {};
wrapper.find('.kanban-column[data-column-value]')
.each(function() {
var col_name = $(this).data().columnValue;
order[col_name] = [];
$(this).find('.kanban-card-wrapper').each(function() {
var card_name = $(this).data().name;
order[col_name].push(card_name);
});
});
fluxify.doAction('update_order', order);
fluxify.doAction('update_order');
},
onAdd: function() {
},
@ -470,11 +484,11 @@ frappe.provide("frappe.views");
// not already working -- double entry
e.preventDefault();
var card_title = $textarea.val();
$new_card_area.hide();
$textarea.val('');
fluxify.doAction('add_card', card_title, column.title)
.then(() => {
$btn_add.show();
$new_card_area.hide();
$textarea.val('');
});
}
}
@ -531,7 +545,8 @@ frappe.provide("frappe.views");
function make_dom() {
var opts = {
name: card.name,
title: remove_img_tags(card.title)
title: remove_img_tags(card.title),
disable_click: card._disable_click ? 'disable-click' : ''
};
self.$card = $(frappe.render_template('kanban_card', opts))
.appendTo(wrapper);

View file

@ -1,4 +1,4 @@
<div class="kanban-card-wrapper" data-name="{{name}}">
<div class="kanban-card-wrapper {{ disable_click }}" data-name="{{name}}">
<div class="kanban-card content">
<div class="kanban-card-title">
{{ title }}

View file

@ -639,7 +639,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
const format_cell = (value, row, column, data) => {
return frappe.format(value == null ? '' : value, column,
return frappe.format(value, column,
{for_print: false, always_show_decimals: true}, data);
};
@ -992,7 +992,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
show_footer_message() {
const message = __('For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10).');
const execution_time_msg = __('Exection Time: {0} sec', [this.execution_time || 0.1]);
const execution_time_msg = __('Execution Time: {0} sec', [this.execution_time || 0.1]);
this.page.footer.removeClass('hide').addClass('text-muted col-md-12')
.html(`<span class="text-left col-md-6">${message}</span><span class="text-right col-md-6">${execution_time_msg}</span>`);

View file

@ -304,3 +304,7 @@ a.no-decoration& {
.text-small {
font-size: @text-small;
}
.disable-click {
pointer-events: none;
}

View file

@ -82,12 +82,22 @@ class TestPermissions(unittest.TestCase):
self.assertFalse("-test-blog-post" in names)
def test_default_values(self):
doc = frappe.new_doc("Blog Post")
self.assertFalse(doc.get("blog_category"))
# Fetch default based on single user permission
add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com")
frappe.set_user("test2@example.com")
doc = frappe.new_doc("Blog Post")
self.assertEqual(doc.get("blog_category"), "_Test Blog Category 1")
# Don't fetch default if user permissions is more than 1
add_user_permission("Blog Category", "_Test Blog Category", "test2@example.com", ignore_permissions=True)
frappe.clear_cache()
doc = frappe.new_doc("Blog Post")
self.assertFalse(doc.get("blog_category"))
def test_user_link_match_doc(self):
blogger = frappe.get_doc("Blogger", "_Test Blogger 1")
blogger.user = "test2@example.com"

View file

@ -90,13 +90,13 @@ def download_pdf(doctype, name, format=None, doc=None, no_letterhead=0):
html = frappe.get_print(doctype, name, format, doc=doc, no_letterhead=no_letterhead)
frappe.local.response.filename = "{name}.pdf".format(name=name.replace(" ", "-").replace("/", "-"))
frappe.local.response.filecontent = get_pdf(html)
frappe.local.response.type = "download"
frappe.local.response.type = "pdf"
@frappe.whitelist()
def report_to_pdf(html, orientation="Landscape"):
frappe.local.response.filename = "report.pdf"
frappe.local.response.filecontent = get_pdf(html, {"orientation": orientation})
frappe.local.response.type = "download"
frappe.local.response.type = "pdf"
@frappe.whitelist()
def print_by_server(doctype, name, print_format=None, doc=None, no_letterhead=0):

View file

@ -42,6 +42,7 @@ def build_response(response_type=None):
'txt': as_txt,
'download': as_raw,
'json': as_json,
'pdf': as_pdf,
'page': as_page,
'redirect': redirect,
'binary': as_binary
@ -84,6 +85,13 @@ def as_json():
response.data = json.dumps(frappe.local.response, default=json_handler, separators=(',',':'))
return response
def as_pdf():
response = Response()
response.mimetype = "application/pdf"
response.headers["Content-Disposition"] = ("filename=\"%s\"" % frappe.response['filename'].replace(' ', '_')).encode("utf-8")
response.data = frappe.response['filecontent']
return response
def as_binary():
response = Response()
response.mimetype = 'application/octet-stream'

View file

@ -34,8 +34,8 @@ ndg-httpsclient
pyasn1
zxcvbn-python
unittest-xml-reporting
oauthlib==2.1.0
requests-oauthlib==1.1.0
oauthlib
requests-oauthlib
pdfkit
PyJWT
PyPDF2