Merge branch 'develop' into ci/reports

This commit is contained in:
mathieu.brunot 2019-11-26 03:12:02 +01:00
commit 5d12770de8
No known key found for this signature in database
GPG key ID: 81584BEAF692D7E0
55 changed files with 735 additions and 662 deletions

View file

@ -0,0 +1,55 @@
context('Control Barcode', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk');
});
function get_dialog_with_barcode() {
return cy.dialog({
title: 'Barcode',
fields: [
{
label: 'Barcode',
fieldname: 'barcode',
fieldtype: 'Barcode'
}
]
});
}
it('should generate barcode on setting a value', () => {
get_dialog_with_barcode().as('dialog');
cy.get('.frappe-control[data-fieldname=barcode] input')
.focus()
.type('123456789')
.blur();
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
.should('exist');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('barcode');
expect(value).to.contain('<svg');
expect(value).to.contain('data-barcode-value="123456789"');
});
});
it('should reset when input is cleared', () => {
get_dialog_with_barcode().as('dialog');
cy.get('.frappe-control[data-fieldname=barcode] input')
.focus()
.type('123456789')
.blur();
cy.get('.frappe-control[data-fieldname=barcode] input')
.clear()
.blur();
cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
.should('not.exist');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('barcode');
expect(value).to.equal('');
});
});
});

View file

@ -61,12 +61,18 @@ context('Control Link', () => {
cy.server();
cy.route('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.get('@todos').then(todos => {
cy.get('.frappe-control[data-fieldname=link] input').type(todos[0]).blur();
cy.get('.frappe-control[data-fieldname=link] input').as('input');
cy.get('@input').focus();
cy.wait('@search_link');
cy.get('@input').type(todos[0]).blur();
cy.wait('@validate_link');
cy.get('.frappe-control[data-fieldname=link] input').focus();
cy.get('.frappe-control[data-fieldname=link] .link-btn').click();
cy.get('@input').focus();
cy.get('.frappe-control[data-fieldname=link] .link-btn')
.should('be.visible')
.click();
cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`);
});
});

View file

@ -290,7 +290,7 @@ def log(msg):
debug_log.append(as_unicode(msg))
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False):
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None):
"""Print a message to the user (via HTTP response).
Messages are sent in the `__server_messages` property in the
response JSON and shown in a pop-up / modal.
@ -299,6 +299,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
:param title: [optional] Message title.
:param raise_exception: [optional] Raise given exception and show message.
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
:param primary_action: [optional] Bind a primary server/client side action.
"""
from frappe.utils import encode
@ -338,6 +339,9 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
if alert:
out.alert = 1
if primary_action:
out.primary_action = primary_action
message_log.append(json.dumps(out))
if raise_exception and hasattr(raise_exception, '__name__'):

View file

@ -22,8 +22,7 @@
"fieldtype": "Link",
"label": "User",
"options": "User",
"reqd": 1,
"unique": 1
"reqd": 1
},
{
"default": "Online",

View file

@ -16,8 +16,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Token",
"reqd": 1,
"unique": 1
"reqd": 1
},
{
"fieldname": "ip_address",

View file

@ -145,30 +145,10 @@ def get_list_context(context=None):
def get_address_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by = None):
from frappe.www.list import get_list
user = frappe.session.user
ignore_permissions = False
if is_website_user():
if not filters: filters = []
add_name = []
contact = frappe.db.sql("""
select
address.name
from
`tabDynamic Link` as link
join
`tabAddress` as address on link.parent = address.name
where
link.parenttype = 'Address' and
link_name in(
select
link.link_name from `tabContact` as contact
join
`tabDynamic Link` as link on contact.name = link.parent
where
contact.user = %s)""",(user))
for c in contact:
add_name.append(c[0])
filters.append(("Address", "name", "in", add_name))
ignore_permissions = True
ignore_permissions = True
if not filters: filters = []
filters.append(("Address", "owner", "=", user))
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)

View file

@ -81,9 +81,9 @@ def get_feed_match_conditions(user=None, doctype='Comment'):
if user_permissions:
can_read_docs = []
for doctype, obj in user_permissions.items():
for dt, obj in user_permissions.items():
for n in obj:
can_read_docs.append('{}|{}'.format(doctype, frappe.db.escape(n.get('doc', ''))))
can_read_docs.append('{}|{}'.format(frappe.db.escape(dt), frappe.db.escape(n.get('doc', ''))))
if can_read_docs:
conditions.append("concat_ws('|', `tab{doctype}`.reference_doctype, `tab{doctype}`.reference_name) in ({values})".format(

View file

@ -18,6 +18,10 @@ frappe.ui.form.on("Communication", {
frm.convert_to_click && frm.set_convert_button();
frm.subject_field = "subject";
// content field contains weird table html that does not render well in Quill
// this field is not to be edited directly anyway, so setting it as read only
frm.set_df_property('content', 'read_only', 1);
if(frm.doc.reference_doctype && frm.doc.reference_name) {
frm.add_custom_button(__(frm.doc.reference_name), function() {
frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name);

View file

@ -3,19 +3,20 @@
frappe.ui.form.on('Data Import', {
onload: function(frm) {
if(frm.doc.__islocal) {
if (frm.doc.__islocal) {
frm.set_value("action", "");
}
frappe.call({
method: "frappe.core.doctype.data_import.data_import.get_importable_doc",
method: "frappe.core.doctype.data_import.data_import.get_importable_doctypes",
callback: function (r) {
let importable_doctypes = r.message;
frm.set_query("reference_doctype", function () {
return {
"filters": {
"issingle": 0,
"istable": 0,
"name": ['in', r.message]
"name": ['in', importable_doctypes]
}
};
});

View file

@ -30,9 +30,8 @@ class DataImport(Document):
@frappe.whitelist()
def get_importable_doc():
import_lst = frappe.cache().hget("can_import", frappe.session.user)
return import_lst
def get_importable_doctypes():
return frappe.cache().hget("can_import", frappe.session.user)
@frappe.whitelist()
def import_data(data_import):

View file

@ -30,7 +30,7 @@ class Report(Document):
if self.is_standard == "No":
# allow only script manager to edit scripts
if frappe.session.user!="Administrator":
if self.report_type != 'Report Builder':
frappe.only_for('Script Manager', True)
if frappe.db.get_value("Report", self.name, "is_standard") == "Yes":

View file

@ -56,8 +56,10 @@ def get_server_script_map():
script_map = frappe.cache().get_value('server_script_map')
if script_map is None:
script_map = {}
for script in frappe.get_all('Server Script', ('name', 'reference_doctype', 'doctype_event',
'api_method', 'script_type')):
enabled_server_scripts = frappe.get_all('Server Script',
fields=('name', 'reference_doctype', 'doctype_event','api_method', 'script_type'),
filters={'disabled': 0})
for script in enabled_server_scripts:
if script.script_type == 'DocType Event':
script_map.setdefault(script.reference_doctype, {}).setdefault(script.doctype_event, []).append(script.name)
else:

View file

@ -8,6 +8,7 @@ from frappe.utils import cint, has_gravatar, format_datetime, now_datetime, get_
from frappe import throw, msgprint, _
from frappe.utils.password import update_password as _update_password
from frappe.desk.notifications import clear_notifications
from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings
from frappe.utils.user import get_system_managers
from bs4 import BeautifulSoup
import frappe.permissions
@ -46,6 +47,9 @@ class User(Document):
self.flags.in_insert = True
throttle_user_creation()
def after_insert(self):
create_notification_settings(self.name)
def validate(self):
self.check_demo()
@ -364,6 +368,9 @@ class User(Document):
if frappe.db.exists("Chat Profile", old_name):
frappe.rename_doc("Chat Profile", old_name, new_name, force=True)
if frappe.db.exists("Notification Settings", old_name):
frappe.rename_doc("Notification Settings", old_name, new_name, force=True)
# set email
frappe.db.sql("""UPDATE `tabUser`
SET email = %s

View file

@ -1,3 +1,7 @@
.version-info {
overflow: auto;
}
.version-info pre {
border: 0px;
margin: 0px;
@ -14,4 +18,4 @@
.version-info .danger {
background-color: #f2dede !important;
}
}

View file

@ -10,7 +10,7 @@
"email_content",
"column_break_4",
"document_type",
"seen",
"read",
"document_name",
"from_user"
],
@ -57,14 +57,6 @@
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Seen"
},
{
"fieldname": "document_name",
"fieldtype": "Data",
@ -79,11 +71,19 @@
"options": "User",
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"fieldname": "read",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 1,
"label": "Read"
}
],
"in_create": 1,
"modified": "2019-10-23 12:48:01.119356",
"modified_by": "Administrator",
"modified": "2019-11-12 15:22:35.283678",
"modified_by": "umair@erpnext.com",
"module": "Desk",
"name": "Notification Log",
"owner": "Administrator",

View file

@ -7,11 +7,12 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled,
is_email_notifications_enabled, is_email_notifications_enabled_for_type)
is_email_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value)
class NotificationLog(Document):
def after_insert(self):
frappe.publish_realtime('notification', after_commit=True, user=self.for_user)
set_notifications_as_unseen(self.for_user)
if is_email_notifications_enabled(self.for_user):
send_notification_email(self)
@ -64,13 +65,13 @@ def make_notification_logs(doc, users):
if is_notifications_enabled(user):
if doc.type == 'Energy Point' and not is_energy_point_enabled():
return
else:
_doc = frappe.new_doc('Notification Log')
_doc.update(doc)
_doc.for_user = user
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point':
_doc.insert(ignore_permissions=True)
_doc = frappe.new_doc('Notification Log')
_doc.update(doc)
_doc.for_user = user
_doc.subject = _doc.subject.replace('<div>', '').replace('</div>', '')
if _doc.for_user != _doc.from_user or doc.type == 'Energy Point':
_doc.insert(ignore_permissions=True)
def send_notification_email(doc):
is_type_enabled = is_email_notifications_enabled_for_type(doc.for_user, doc.type)
@ -112,11 +113,25 @@ def get_email_header(doc):
@frappe.whitelist()
def mark_as_seen(docname):
if docname:
frappe.db.set_value('Notification Log', docname, 'seen', 1, update_modified=False)
def mark_all_as_read():
unread_docs_list = frappe.db.get_all('Notification Log', filters = {'read': 0, 'for_user': frappe.session.user})
unread_docnames = [doc.name for doc in unread_docs_list]
if unread_docnames:
filters = {'name': ['in', unread_docnames]}
frappe.db.set_value('Notification Log', filters, 'read', 1, update_modified=False)
@frappe.whitelist()
def mark_as_read(docname):
if docname:
frappe.db.set_value('Notification Log', docname, 'read', 1, update_modified=False)
@frappe.whitelist()
def trigger_indicator_hide():
frappe.publish_realtime('indicator_hide', user=frappe.session.user)
def set_notifications_as_unseen(user):
try:
frappe.db.set_value('Notification Settings', user, 'seen', 0)
except frappe.DoesNotExistError:
return

View file

@ -13,7 +13,8 @@
"enable_email_assignment",
"enable_email_energy_point",
"enable_email_share",
"user"
"user",
"seen"
],
"fields": [
{
@ -72,14 +73,20 @@
"fieldname": "user",
"fieldtype": "Link",
"hidden": 1,
"in_list_view": 1,
"label": "User",
"options": "User",
"read_only": 1
},
{
"default": "0",
"fieldname": "seen",
"fieldtype": "Check",
"hidden": 1,
"label": "Seen"
}
],
"in_create": 1,
"modified": "2019-10-23 12:42:56.175928",
"modified": "2019-11-19 12:57:59.356786",
"modified_by": "Administrator",
"module": "Desk",
"name": "Notification Settings",

View file

@ -31,12 +31,12 @@ def is_email_notifications_enabled_for_type(user, notification_type):
return True
return enabled
@frappe.whitelist()
def create_notification_settings():
_doc = frappe.new_doc('Notification Settings')
_doc.name = frappe.session.user
_doc.insert(ignore_permissions=True)
frappe.db.commit()
def create_notification_settings(user):
if not frappe.db.exists("Notification Settings", user):
_doc = frappe.new_doc('Notification Settings')
_doc.name = user
_doc.insert(ignore_permissions=True)
frappe.db.commit()
@frappe.whitelist()
@ -60,3 +60,7 @@ def get_permission_query_conditions(user):
if not user: user = frappe.session.user
return '''(`tabNotification Settings`.user = '{user}')'''.format(user=user)
@frappe.whitelist()
def set_seen_value(value, user):
frappe.db.set_value('Notification Settings', user, 'seen', value, update_modified=False)

View file

@ -184,7 +184,7 @@ frappe.ui.form.on("Email Account", {
read as well as unread message from server. This may also cause the duplication\
of Communication (emails).");
frappe.confirm(msg, null, function() {
frm.set_value("email_sync_option", "UNSEEN");
frm.set_value("email_sync_option", "ALL");
});
}
}

View file

@ -298,7 +298,7 @@ class EmailServer:
"Connection timed out",
)
for message in messages:
if message in strip(cstr(e.message)) or message in strip(cstr(getattr(e, 'strerror', ''))):
if message in strip(cstr(e)) or message in strip(cstr(getattr(e, 'strerror', ''))):
return True
return False

View file

@ -180,6 +180,33 @@ class RazorpaySettings(Document):
integration_request = create_request_log(kwargs, "Host", "Razorpay")
return get_url("./integrations/razorpay_checkout?token={0}".format(integration_request.name))
def create_order(self, **kwargs):
# Creating Orders https://razorpay.com/docs/api/orders/
# convert rupees to paisa
kwargs['amount'] *= 100
# Create integration log
integration_request = create_request_log(kwargs, "Host", "Razorpay")
# Setup payment options
payment_options = {
"amount": kwargs.get('amount'),
"currency": kwargs.get('currency', 'INR'),
"receipt": kwargs.get('receipt'),
"payment_capture": kwargs.get('payment_capture')
}
if self.api_key and self.api_secret:
try:
order = make_post_request("https://api.razorpay.com/v1/orders",
auth=(self.api_key, self.get_password(fieldname="api_secret", raise_exception=False)),
data=payment_options)
order['integration_request'] = integration_request.name
return order # Order returned to be consumed by razorpay.js
except Exception:
frappe.log(frappe.get_traceback())
frappe.throw(_("Could not create razorpay order"))
def create_request(self, data):
self.data = frappe._dict(data)
@ -213,6 +240,10 @@ class RazorpaySettings(Document):
self.integration_request.update_status(data, 'Authorized')
self.flags.status_changed_to = "Authorized"
if resp.get("status") == "captured":
self.integration_request.update_status(data, 'Completed')
self.flags.status_changed_to = "Completed"
elif data.get('subscription_id'):
if resp.get("status") == "refunded":
# if subscription start date is in future then
@ -222,14 +253,6 @@ class RazorpaySettings(Document):
self.integration_request.update_status(data, 'Completed')
self.flags.status_changed_to = "Verified"
if resp.get("status") == "captured":
# if subscription starts immediately then
# razorpay charge the actual amount
# thus changing status to Completed
self.integration_request.update_status(data, 'Completed')
self.flags.status_changed_to = "Completed"
else:
frappe.log_error(str(resp), 'Razorpay Payment not authorized')
@ -242,7 +265,6 @@ class RazorpaySettings(Document):
redirect_to = data.get('redirect_to') or None
redirect_message = data.get('redirect_message') or None
if self.flags.status_changed_to in ("Authorized", "Verified", "Completed"):
if self.data.reference_doctype and self.data.reference_docname:
custom_redirect_to = None
@ -330,6 +352,63 @@ def capture_payment(is_sandbox=False, sanbox_response=None):
doc.error = frappe.get_traceback()
frappe.log_error(doc.error, '{0} Failed'.format(doc.name))
@frappe.whitelist(allow_guest=True)
def get_api_key():
controller = frappe.get_doc("Razorpay Settings")
return controller.api_key
@frappe.whitelist(allow_guest=True)
def get_order(doctype, docname):
# Order returned to be consumed by razorpay.js
doc = frappe.get_doc(doctype, docname)
try:
# Do not use run_method here as it fails silently
return doc.get_razorpay_order()
except AttributeError:
frappe.log_error(frappe.get_traceback(), _("Controller method get_razorpay_order missing"))
frappe.throw(_("Could not create Razorpay order. Please contact Administrator"))
@frappe.whitelist(allow_guest=True)
def order_payment_success(integration_request, params):
"""Called by razorpay.js on order payment success, the params
contains razorpay_payment_id, razorpay_order_id, razorpay_signature
that is updated in the data field of integration request
Args:
integration_request (string): Name for integration request doc
params (string): Params to be updated for integration request.
"""
params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request)
# Update integration request
integration.update_status(params, integration.status)
integration.reload()
data = json.loads(integration.data)
controller = frappe.get_doc("Razorpay Settings")
# Update payment and integration data for payment controller object
controller.integration_request = integration
controller.data = frappe._dict(data)
# Authorize payment
controller.authorize_payment()
@frappe.whitelist(allow_guest=True)
def order_payment_failure(integration_request, params):
"""Called by razorpay.js on failure
Args:
integration_request (TYPE): Description
params (TYPE): error data to be updated
"""
frappe.log_error(params, 'Razorpay Payment Failure')
params = json.loads(params)
integration = frappe.get_doc("Integration Request", integration_request)
integration.update_status(params, integration.status)
def convert_rupee_to_paisa(**kwargs):
for addon in kwargs.get('addons'):
addon['item']['amount'] *= 100
@ -383,4 +462,4 @@ def validate_payment_callback(data):
_throw()
def handle_subscription_notification(doctype, docname):
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)
call_hook_method("handle_subscription_notification", doctype=doctype, docname=docname)

View file

@ -253,7 +253,7 @@ frappe.patches.v12_0.move_email_and_phone_to_child_table
frappe.patches.v12_0.delete_duplicate_indexes
frappe.patches.v12_0.set_default_incoming_email_port
frappe.patches.v12_0.update_global_search
execute:frappe.reload_doc('desk', 'doctype', 'notification_settings')
frappe.patches.v12_0.setup_tags
frappe.patches.v12_0.update_auto_repeat_status_and_not_submittable
frappe.patches.v12_0.copy_to_parent_for_tags
frappe.patches.v12_0.create_notification_settings_for_user

View file

@ -0,0 +1,11 @@
from __future__ import unicode_literals
import frappe
from frappe.desk.doctype.notification_settings.notification_settings import create_notification_settings
def execute():
frappe.reload_doc('desk', 'doctype', 'notification_settings')
frappe.reload_doc('desk', 'doctype', 'notification_subscribed_document')
users = frappe.db.get_all('User', fields=['name'])
for user in users:
create_notification_settings(user.name)

View file

@ -18,6 +18,9 @@
"js/frappe-recorder.min.js": [
"public/js/frappe/recorder/recorder.js"
],
"js/checkout.min.js": [
"public/js/integrations/razorpay.js"
],
"js/frappe-web.min.js": [
"public/js/frappe/class.js",
"public/js/frappe/polyfill.js",

View file

@ -136,11 +136,7 @@ frappe.Application = Class.extend({
method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status',
callback: function(r) {
if (r.message[0] == __("Inactive")) {
frappe.msgprint({
title: __("Scheduler Inactive"),
indicator: "red",
message: __("Background jobs are not running. Please contact Administrator")
});
frappe.call('frappe.utils.scheduler.activate_scheduler');
}
}
});

View file

@ -367,6 +367,13 @@ export default {
if (this.on_success) {
this.on_success(file_doc, r);
}
} else if (xhr.status === 403) {
let response = JSON.parse(xhr.responseText);
frappe.msgprint({
title: __('Not permitted'),
indicator: 'red',
message: response._error_message
});
} else {
file.failed = true;
let error = null;

View file

@ -1,18 +1,27 @@
import JsBarcode from "jsbarcode";
import JsBarcode from 'jsbarcode';
frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({
make_wrapper() {
// Create the elements for barcode area
this._super();
this.default_svg = '<svg height=80></svg>';
let $input_wrapper = this.$wrapper.find('.control-input-wrapper');
this.barcode_area = $(`<div class="barcode-wrapper border"><svg height=80></svg></div>`);
this.barcode_area = $(
`<div class="barcode-wrapper border">${this.default_svg}</div>`
);
this.barcode_area.appendTo($input_wrapper);
},
parse(value) {
// Parse raw value
return value ? this.get_barcode_html(value) : "";
if (value) {
if (value.startsWith('<svg')) {
return value;
}
return this.get_barcode_html(value);
}
return '';
},
set_formatted_input(value) {
@ -20,47 +29,39 @@ frappe.ui.form.ControlBarcode = frappe.ui.form.ControlData.extend({
let svg = value;
const barcode_value = $(svg).attr('data-barcode-value');
if(!barcode_value) {
if (!barcode_value && this.doc) {
svg = this.get_barcode_html(value);
this.doc[this.df.fieldname] = svg;
}
this.$input.val(barcode_value || value);
this.barcode_area.html(svg);
this.barcode_area.html(svg || this.default_svg);
},
get_barcode_html(value) {
// Get svg
const svg = this.barcode_area.find('svg')[0];
JsBarcode(svg, value, this.get_options(value));
$(svg).attr('data-barcode-value', value);
return this.barcode_area.html();
if (value) {
// Get svg
const svg = this.barcode_area.find('svg')[0];
JsBarcode(svg, value, this.get_options(value));
$(svg).attr('data-barcode-value', value);
return this.barcode_area.html();
}
},
get_options(value) {
// get JsBarcode options
let options = JSON.parse('{ "height" : 40 }');
if (this.isValidJson(this.df.options)) {
if (frappe.utils.is_json(this.df.options)) {
options = JSON.parse(this.df.options);
if (options.format && options.format === "EAN") {
options.format = value.length == 8 ? "EAN8" : "EAN13";
if (options.format && options.format === 'EAN') {
options.format = value.length == 8 ? 'EAN8' : 'EAN13';
}
if (options.valueField) {
// Set companion field value
this.frm.set_value(options.valueField, value);
this.frm && this.frm.set_value(options.valueField, value);
}
}
return options;
},
isValidJson(jsonData) {
try {
JSON.parse(jsonData);
return true;
} catch (e) {
return false;
}
}
});

View file

@ -28,6 +28,7 @@ frappe.ui.form.ControlCheck = frappe.ui.form.ControlData.extend({
return cint(value);
},
set_input: function(value) {
value = cint(value);
if(this.input) {
this.input.checked = (value ? 1 : 0);
}

View file

@ -61,6 +61,9 @@ frappe.ui.form.ControlMultiSelectList = frappe.ui.form.ControlData.extend({
});
this.$list_wrapper.on('keydown', e => {
if ($(e.target).is('input')) {
return;
}
if (e.key === 'Backspace') {
this.set_value([]);
}

View file

@ -52,7 +52,7 @@ export default class Grid {
let template = `<div class="form-group">
<div class="clearfix">
<label class="control-label" style="padding-right: 0px;">${__(this.df.label)}</label>
<label class="control-label" style="padding-right: 0px;">${__(this.df.label || '')}</label>
</div>
<div class="form-grid">
<div class="grid-heading-row"></div>

View file

@ -614,7 +614,7 @@ class FilterArea {
let options = df.options;
let condition = '=';
let fieldtype = df.fieldtype;
if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) {
if (['Text', 'Small Text', 'Text Editor', 'Data', 'Code'].includes(fieldtype)) {
fieldtype = 'Data';
condition = 'like';
}
@ -625,17 +625,13 @@ class FilterArea {
options = options.join("\n");
}
}
let default_value = (fieldtype === 'Link') ? frappe.defaults.get_user_default(options) : null;
if (['__default', '__global'].includes(default_value)) {
default_value = null;
}
return {
fieldtype: fieldtype,
label: __(df.label),
options: options,
fieldname: df.fieldname,
condition: condition,
default: default_value,
onchange: () => this.refresh_list_view(),
ignore_link_validation: fieldtype === 'Dynamic Link'
};
@ -650,6 +646,13 @@ class FilterArea {
for (let key in fields_dict) {
let field = fields_dict[key];
let value = field.get_value();
let default_value = (field.df.fieldtype === 'Link') ?
frappe.defaults.get_user_default(field.df.options) : null;
if (['__default', '__global'].includes(default_value)) {
default_value = null;
}
if (value) {
if (field.df.condition === 'like' && !value.includes('%')) {
value = '%' + value + '%';
@ -660,6 +663,13 @@ class FilterArea {
field.df.condition || '=',
value
]);
} else if (default_value) {
filters.push([
this.list_view.doctype,
field.df.fieldname,
field.df.condition,
default_value
]);
}
}

View file

@ -1077,10 +1077,21 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
});
this.toggle_result_area();
this.render_list();
if (this.$checks.length) {
this.set_rows_as_checked();
}
});
});
}
set_rows_as_checked() {
$.each(this.$checks, (i, el) => {
let docname = $(el).attr('data-name');
this.$result.find(`.list-row-checkbox[data-name='${docname}']`).prop('checked', true);
});
this.on_row_checked();
}
on_row_checked() {
this.$list_head_subject = this.$list_head_subject || this.$result.find('header .list-header-subject');
this.$checkbox_actions = this.$checkbox_actions || this.$result.find('header .checkbox-actions');

View file

@ -525,7 +525,13 @@ $.extend(frappe.model, {
},
delete_doc: function(doctype, docname, callback) {
frappe.confirm(__("Permanently delete {0}?", [docname]), function() {
var title = docname;
var title_field = frappe.get_meta(doctype).title_field;
if (frappe.get_meta(doctype).autoname == "hash" && title_field) {
var title = frappe.model.get_value(doctype, docname, title_field);
title += " (" + docname + ")";
}
frappe.confirm(__("Permanently delete {0}?", [title]), function() {
return frappe.call({
method: 'frappe.client.delete',
args: {

View file

@ -120,12 +120,6 @@ frappe.msgprint = function(msg, title) {
}
});
// setup and bind an action to the primary button
if (data.primary_action) {
frappe.msg_dialog.set_primary_action(__(data.primary_action.label || "Done"),
data.primary_action.action);
}
// class "msgprint" is used in tests
frappe.msg_dialog.msg_area = $('<div class="msgprint">')
.appendTo(frappe.msg_dialog.body);
@ -137,6 +131,43 @@ frappe.msgprint = function(msg, title) {
frappe.msg_dialog.indicator = frappe.msg_dialog.header.find('.indicator');
}
// setup and bind an action to the primary button
if (data.primary_action) {
if (data.primary_action.server_action && typeof data.primary_action.server_action === 'string') {
data.primary_action.action = () => {
frappe.call({
method: data.primary_action.server_action,
args: {
args: data.primary_action.args
}
});
}
}
if (data.primary_action.client_action && typeof data.primary_action.client_action === 'string') {
let parts = data.primary_action.client_action.split('.');
let obj = window;
for (let part of parts) {
obj = obj[part];
}
data.primary_action.action = () => {
if (typeof obj === 'function') {
obj(data.primary_action.args);
}
}
}
frappe.msg_dialog.set_primary_action(
__(data.primary_action.label || "Done"),
data.primary_action.action
);
} else {
if (frappe.msg_dialog.has_primary_action) {
frappe.msg_dialog.get_primary_btn().addClass('hide');
frappe.msg_dialog.has_primary_action = false;
}
}
if(data.message==null) {
data.message = '';
}

View file

@ -1,3 +1,5 @@
frappe.provide('frappe.search');
frappe.ui.Notifications = class Notifications {
constructor() {
frappe.model
@ -29,16 +31,25 @@ frappe.ui.Notifications = class Notifications {
);
frappe.utils.bind_actions_with_object(this.$dropdown_list, this);
let me = this;
frappe.search.utils.make_function_searchable(
me.route_to_settings,
__('Notification Settings'),
);
this.setup_notifications();
this.bind_events();
}
route_to_settings() {
frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
}
setup_notifications() {
this.get_notifications_list(this.max_length).then(list => {
this.dropdown_items = list;
this.render_notifications_dropdown();
if (this.$notifications.find('.unseen').length) {
if (this.notifications_settings.seen == 0) {
this.$notification_indicator.show();
}
});
@ -204,7 +215,7 @@ frappe.ui.Notifications = class Notifications {
change_activity_status() {
if (this.$dropdown_list.find('.activity-status')) {
this.$dropdown_list.find('.activity-status').replaceWith(
`<a class="recent-item text-center text-muted"
`<a class="recent-item text-center text-muted"
href="#List/Notification Log">
<div class="full-log-btn">${__('View Full Log')}</div>
</a>`
@ -212,26 +223,44 @@ frappe.ui.Notifications = class Notifications {
}
}
set_field_as_seen(docname, $el) {
set_field_as_read(docname, $el) {
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.mark_as_seen',
'frappe.desk.doctype.notification_log.notification_log.mark_as_read',
{ docname: docname }
).then(()=> {
$el.removeClass('unseen');
$el.removeClass('unread');
});
}
explicitly_mark_as_seen(e, $target) {
explicitly_mark_as_read(e, $target) {
e.preventDefault();
e.stopImmediatePropagation();
let docname = $target.parents('.unseen').attr('data-name');
this.set_field_as_seen(docname, $target.parents('.unseen'));
let docname = $target.parents('.unread').attr('data-name');
this.set_field_as_read(docname, $target.parents('.unread'));
}
mark_as_seen(e, $target) {
mark_as_read(e, $target) {
let docname = $target.attr('data-name');
let df = this.dropdown_items.filter(f => docname.includes(f.name))[0];
this.set_field_as_seen(df.name, $target);
this.set_field_as_read(df.name, $target);
}
mark_all_as_read(e) {
e.stopImmediatePropagation();
this.$dropdown_list.find('.unread').removeClass('unread');
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.mark_all_as_read',
);
}
toggle_seen(flag) {
frappe.call(
'frappe.desk.doctype.notification_settings.notification_settings.set_seen_value',
{
value: cint(flag),
user: frappe.session.user
}
);
}
get_notifications_list(limit) {
@ -279,8 +308,8 @@ frappe.ui.Notifications = class Notifications {
field.document_type,
field.document_name
);
let seen_class = field.seen ? '' : 'unseen';
let mark_seen_action = field.seen ? '': 'data-action="mark_as_seen"';
let read_class = field.read ? '' : 'unread';
let mark_read_action = field.read ? '': 'data-action="mark_as_read"';
let message = field.subject;
let title = message.match(/<b class="subject-title">(.*?)<\/b>/);
message = title ? message.replace(title[1], frappe.ellipsis(title[1], 100)): message;
@ -288,18 +317,18 @@ frappe.ui.Notifications = class Notifications {
let user = field.from_user;
let user_avatar = frappe.avatar(user, 'avatar-small user-avatar');
let timestamp = frappe.datetime.comment_when(field.creation, true);
let item_html =
`<a class="recent-item ${seen_class}"
let item_html =
`<a class="recent-item ${read_class}"
href="${doc_link}"
data-name="${field.name}"
${mark_seen_action}
${mark_read_action}
>
${user_avatar}
${message_html}
<div class="notification-timestamp text-muted">
${timestamp}
</div>
<span class="mark-read text-muted hidden-xs" data-action="explicitly_mark_as_seen">
<span class="mark-read text-muted hidden-xs" data-action="explicitly_mark_as_read">
${__('Mark as Read')}
</span>
</a>`;
@ -329,18 +358,25 @@ frappe.ui.Notifications = class Notifications {
let category_id = frappe.dom.get_unique_id();
let settings_html =
category.value === 'Notifications'
? `<span class="notification-settings pull-right" data-action="make_and_route_to_settings">
? `<span class="notification-settings pull-right" data-action="go_to_settings">
${__('Settings')}
</span>`
: '';
let mark_all_read_html =
category.value === 'Notifications'
? `<span class="mark-all-read pull-right" data-action="mark_all_as_read">
${__('Mark all as Read')}
</span>`
: '';
let html = `<li class="notifications-category">
<li class="text-muted header"
data-action="${category.action}"
href="#${category_id}"
href="#${category_id}"
data-toggle="collapse">
${category.label}
<span class="octicon octicon-chevron-down collapse-indicator"></span>
${settings_html}
${mark_all_read_html}
</li>
<div id="${category_id}" class="collapse category-list" data-category="${category.value}">
<div class="text-center text-muted notifications-loading">
@ -364,20 +400,11 @@ frappe.ui.Notifications = class Notifications {
);
}
make_and_route_to_settings(e) {
go_to_settings(e) {
e.stopImmediatePropagation();
this.$dropdown.removeClass('open');
this.$dropdown.trigger('hide.bs.dropdown');
let method =
'frappe.desk.doctype.notification_settings.notification_settings.create_notification_settings';
return Promise.resolve()
.then(() => {
if (!this.notifications_settings) return frappe.call(method);
})
.then(() => {
frappe.set_route(`#Form/Notification Settings/${frappe.session.user}`);
});
this.route_to_settings();
}
bind_events() {
@ -418,22 +445,14 @@ frappe.ui.Notifications = class Notifications {
});
this.$dropdown.on('hide.bs.dropdown', e => {
let hide = $(e.currentTarget).data('closable');
if (hide) {
this.$dropdown_list
.find('[data-category="Notifications"]')
.collapse('show');
this.$dropdown_list
.find(
'[data-category="Todays Events"], [data-category="Open Documents"]'
)
.collapse('hide');
}
$(e.currentTarget).data('closable', true);
return hide;
});
this.$dropdown.on('show.bs.dropdown', () => {
this.toggle_seen(true);
if (this.$notification_indicator.is(':visible')) {
this.$notification_indicator.hide();
frappe.call(
'frappe.desk.doctype.notification_log.notification_log.trigger_indicator_hide'
);
@ -490,4 +509,4 @@ frappe.ui.notifications = {
}
frappe.set_route('List', doctype);
}
};
};

View file

@ -622,20 +622,21 @@ frappe.search.utils = {
value: this.bolden_match_part(__(item.label), txt),
index: this.fuzzy_search(txt, target),
match: item.label,
onclick: item.action,
onclick: () => item.action.apply(this, item.args)
});
}
});
return results;
},
make_function_searchable(_function, label=null) {
make_function_searchable(_function, label=null, args=null) {
if (typeof _function !== 'function') {
throw new Error('First argument should be a function');
}
this.searchable_functions.push({
'label': label || _function.name,
'action': _function
'action': _function,
'args': args,
});
},
searchable_functions: [],

View file

@ -975,12 +975,15 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
return this.data[index];
}
}).filter(Boolean);
let totalRow = this.datatable.bodyRenderer.getTotalRow().reduce((row, cell) => {
row[cell.column.id] = cell.content;
return row;
}, {});
rows.push(totalRow);
if (this.raw_data.add_total_row) {
let totalRow = this.datatable.bodyRenderer.getTotalRow().reduce((row, cell) => {
row[cell.column.id] = cell.content;
return row;
}, {});
rows.push(totalRow);
}
return rows;
}

View file

@ -500,10 +500,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
axisOptions: {
shortenYAxisNumbers: 1
},
format_tooltip_x: value => value.doc.name,
format_tooltip_y:
value => frappe.format(value, get_df(value.field), { always_show_decimals: true, inline: true }, get_doc(value.doc))
tooltipOptions: {
formatTooltipY: value => frappe.format(value, get_df(this.chart_args.y_axes[0]), { always_show_decimals: true, inline: true }, get_doc(value.doc))
}
});
}
@ -997,7 +996,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
content: d[cdt_field(col.field)],
editable: Boolean(name && this.is_editable(col.docfield, d)),
format: value => {
return frappe.format(value, col.docfield, { always_show_decimals: true });
return frappe.format(value, col.docfield, { always_show_decimals: true }, d);
}
};
}

View file

@ -40,7 +40,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
set_field_values() {
if (this.doc_name) this.set_values(this.doc);
if (this.doc.name) this.set_values(this.doc);
else return;
}

View file

@ -52,12 +52,13 @@ frappe.ready(function() {
const data = setup_fields(r.message);
let web_form_doc = data.web_form;
if (web_form_doc.doc_name && web_form_doc.allow_edit === 0) {
window.location.replace(window.location.pathname + "?new=1");
return;
if (web_form_doc.name && web_form_doc.allow_edit === 0) {
if (!window.location.href.includes("?new=1")) {
window.location.replace(window.location.pathname + "?new=1");
}
}
let doc = r.message.doc || build_doc(r.message);
web_form.prepare(web_form_doc, doc);
web_form.prepare(web_form_doc, r.message.doc && web_form_doc.allow_edit === 1 ? r.message.doc : {});
web_form.make();
web_form.set_default_values();
})

View file

@ -1,26 +1,148 @@
frappe.provide("frappe.integration_service")
/* HOW-TO
frappe.integration_service.razorpay = {
load: function(frm) {
new frappe.integration_service.Razorpay(frm)
},
scheduler_job_helper: function(){
return {
"Every few minutes": "Check and capture new payments"
Razorpay Payment
1. Include checkout script in your code
<script type="text/javascript" src="/assets/js/checkout.min.js"></script>
2. Create the Order controller in your backend
def get_razorpay_order(self):
controller = get_payment_gateway_controller("Razorpay")
payment_details = {
"amount": 300,
...
"reference_doctype": "Conference Participant",
"reference_docname": self.name,
...
"receipt": self.name
}
return controller.create_order(**payment_details)
3. Inititate the payment in client using checkout API
function make_payment(ticket) {
var options = {
"name": "<CHECKOUT MODAL TITLE>",
"description": "<CHECKOUT MODAL DESCRIPTION>",
"image": "<CHECKOUT MODAL LOGO>",
"prefill": {
"name": "<CUSTOMER NAME>",
"email": "<CUSTOMER EMAIL>",
"contact": "<CUSTOMER PHONE>"
},
"theme": {
"color": "<MODAL COLOR>"
},
"doctype": "<REFERENCE DOCTYPE>",
"docname": "<REFERENCE DOCNAME"
};
razorpay = new frappe.checkout.razorpay(options)
razorpay.on_open = () => {
<SCRIPT TO RUN WHEN MODAL OPENS>
}
razorpay.on_success = () => {
<SCRIPT TO RUN ON PAYMENT SUCCESS>
}
razorpay.on_fail = () => {
<SCRIPT TO RUN ON PAYMENT FAILURE>
}
razorpay.init() // Creates the order and opens the modal
}
*/
frappe.provide("frappe.checkout");
frappe.require('https://checkout.razorpay.com/v1/checkout.js').then(() => {
frappe.checkout.razorpay = class RazorpayCheckout {
constructor(opts) {
Object.assign(this, opts);
}
init() {
frappe.run_serially([
() => this.get_key(),
() => this.make_order(),
() => this.prepare_options(),
() => this.setup_handler(),
() => this.show()
]);
}
show() {
this.razorpay = new Razorpay(this.options);
this.razorpay.once('ready', (response) => {
this.on_open && this.on_open(response);
})
this.razorpay.open();
}
get_key() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_api_key").then(res => {
this.key = res.message;
resolve(true);
})
});
}
make_order() {
return new Promise(resolve => {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.get_order", {
doctype: this.doctype,
docname: this.docname
}).then(res => {
this.order = res.message;
resolve(true);
})
});
}
order_success(response) {
frappe.call("frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_success", {
integration_request: this.order.integration_request,
params: {
razorpay_payment_id: response.razorpay_payment_id,
razorpay_order_id: response.razorpay_order_id,
razorpay_signature: response.razorpay_signature
}
})
}
order_fail(response) {
frappe.call( "frappe.integrations.doctype.razorpay_settings.razorpay_settings.order_payment_failure", {
integration_request: this.order.integration_request,
params: response
})
}
prepare_options() {
this.options = {
"key": this.key,
"amount": this.order.amount_due,
"currency": this.order.currency,
"name": this.name,
"description": this.description,
"image": this.image,
"order_id": this.order.id,
"prefill": this.prefill,
"theme": this.theme,
"modal": this.modal
};
}
setup_handler() {
this.options.handler = (response) => {
if (response.error) {
this.order_fail(response);
this.on_fail && this.on_fail(response);
}
else if (response.razorpay_payment_id) {
this.order_success(response);
this.on_success && this.on_success(response);
}
}
}
}
}
frappe.integration_service.Razorpay = Class.extend({
init:function(frm){
this.frm = frm;
this.frm.toggle_display("use_test_account", false);
this.show_logs();
},
show_logs: function(){
this.frm.add_custom_button(__("Show Log"), function(frm){
frappe.route_options = {"integration_request_service": "Razorpay"};
frappe.set_route("List", "Integration Request");
});
}
})
});

View file

@ -7,6 +7,11 @@
cursor: pointer;
}
.mark-all-read {
margin-top: 2px;
margin-right: 15px;
}
.notification-settings {
margin-top: 2px;
}
@ -35,6 +40,10 @@
margin-left: 150px;
}
.navbar .dropdown-notifications .notifications-icon {
padding-top: 9px;
}
.notifications-indicator {
font-size: 7px;
position: absolute;
@ -64,7 +73,7 @@ a.recent-item:hover {
background-color: #f0f4f7;
}
a.unseen:hover .mark-read {
a.unread:hover .mark-read {
display: inline-block;
}
@ -89,7 +98,7 @@ a.unseen:hover .mark-read {
font-weight: 500;
}
.unseen {
.unread {
background: @light-yellow;
}

View file

@ -101,6 +101,10 @@ h4.modal-title {
font-size: 1em;
}
h5.modal-title {
margin: 0px !important;
}
.col-xs-1 { @extend .col-1; }
.col-xs-2 { @extend .col-2; }
.col-xs-3 { @extend .col-3; }

View file

@ -21,14 +21,15 @@ frappe.ui.form.on('Energy Point Log', {
reqd: 1
}],
primary_action: (values) => {
return frappe.xcall('frappe.social.doctype.energy_point_log.energy_point_log.revert', {
'name': frm.doc.name,
return frm.call('revert', {
'reason': values.reason
}).then(revert_log => {
}).then(res => {
let revert_log = res.message;
revert_dialog.hide();
revert_dialog.clear();
frappe.model.docinfo[frm.doc.reference_doctype][frm.doc.reference_name].energy_point_logs.unshift(revert_log);
}).catch(() => {});
frm.refresh();
});
},
primary_action_label: __('Submit')
});

View file

@ -44,6 +44,36 @@ class EnergyPointLog(Document):
enqueue_create_notification(self.user, notification_doc)
def on_trash(self):
if self.type == 'Revert':
reference_log = frappe.get_doc('Energy Point Log', self.revert_of)
reference_log.reverted = 0
reference_log.save()
def revert(self, reason):
frappe.only_for('System Manager')
if self.type != 'Auto':
frappe.throw(_('This document cannot be reverted'))
if self.get('reverted'):
return
self.reverted = 1
self.save(ignore_permissions=True)
revert_log = frappe.get_doc({
'doctype': 'Energy Point Log',
'points': -(self.points),
'type': 'Revert',
'user': self.user,
'reason': reason,
'reference_doctype': self.reference_doctype,
'reference_name': self.reference_name,
'revert_of': self.name
}).insert(ignore_permissions=True)
return revert_log
def get_notification_message(doc):
owner_name = get_fullname(doc.owner)
points = doc.points
@ -149,7 +179,8 @@ def check_if_log_exists(ref_doctype, ref_name, rule, user=None):
filters = frappe._dict({
'rule': rule,
'reference_doctype': ref_doctype,
'reference_name': ref_name
'reference_name': ref_name,
'reverted': 0
})
if user:
@ -257,32 +288,6 @@ def get_reviews(doctype, docname):
'type': ['in', ('Appreciation', 'Criticism')],
}, fields=['points', 'owner', 'type', 'user', 'reason', 'creation'])
@frappe.whitelist()
def revert(name, reason):
frappe.only_for('System Manager')
doc_to_revert = frappe.get_doc('Energy Point Log', name)
if doc_to_revert.type != 'Auto':
frappe.throw(_('This document cannot be reverted'))
if doc_to_revert.reverted: return
doc_to_revert.reverted = 1
doc_to_revert.save(ignore_permissions=True)
revert_log = frappe.get_doc({
'doctype': 'Energy Point Log',
'points': -(doc_to_revert.points),
'type': 'Revert',
'user': doc_to_revert.user,
'reason': reason,
'reference_doctype': doc_to_revert.reference_doctype,
'reference_name': doc_to_revert.reference_name,
'revert_of': doc_to_revert.name
}).insert(ignore_permissions=True)
return revert_log
def send_weekly_summary():
send_summary('Weekly')
@ -325,5 +330,3 @@ def get_footer_message(timespan):
return _("Stats based on last month's performance (from {0} to {1})")
else:
return _("Stats based on last week's performance (from {0} to {1})")

View file

@ -249,6 +249,27 @@ class TestEnergyPointLog(unittest.TestCase):
# point should not be awarded more than once for same doc (irrespective of user)
self.assertEqual(second_user_points_after_closing_todo, second_user_points)
def test_allow_creation_of_new_log_if_the_previous_log_was_reverted(self):
frappe.set_user('test@example.com')
todo_point_rule = create_energy_point_rule_for_todo()
energy_point_of_user = get_points('test@example.com')
created_todo = create_a_todo()
created_todo.status = 'Closed'
created_todo.save()
points_after_closing_todo = get_points('test@example.com')
log_name = frappe.db.exists('Energy Point Log', {'reference_name': created_todo.name})
frappe.get_doc('Energy Point Log', log_name).revert('Just for test')
points_after_reverting_todo = get_points('test@example.com')
created_todo.save()
points_after_saving_todo_again = get_points('test@example.com')
rule_points = todo_point_rule.points
self.assertEqual(points_after_closing_todo, energy_point_of_user + rule_points)
self.assertEqual(points_after_reverting_todo, points_after_closing_todo - rule_points)
self.assertEqual(points_after_saving_todo_again, points_after_reverting_todo + rule_points)
def create_energy_point_rule_for_todo(multiplier_field=None, for_doc_event='Custom', max_points=None,
for_assigned_users=0, field_to_check=None, apply_once=False, user_field='owner'):

View file

@ -8,7 +8,8 @@ from frappe import _
import frappe.cache_manager
from frappe.model.document import Document
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
from frappe.social.doctype.energy_point_log.energy_point_log import create_energy_points_log, revert
from frappe.social.doctype.energy_point_log.energy_point_log import \
create_energy_points_log
class EnergyPointRule(Document):
def on_update(self):
@ -106,7 +107,8 @@ def revert_points_for_cancelled_doc(doc):
'type': 'Auto'
})
for log in energy_point_logs:
revert(log.name, _('Reference document has been cancelled'))
reference_log = frappe.get_doc('Energy Point Log', log.name)
reference_log.revert(_('Reference document has been cancelled'))
def get_energy_point_doctypes():

View file

@ -80,7 +80,6 @@ def get_monthly_goal_graph_data(title, doctype, docname, goal_value_field, goal_
if filter_str:
doc_filter += ' and ' + filter_str if doc_filter else filter_str
month_to_value_dict = get_monthly_results(goal_doctype, goal_field, date_field, doc_filter, aggregation)
frappe.db.set_value(doctype, docname, goal_history_field, json.dumps(month_to_value_dict))
month_to_value_dict[current_month_year] = current_month_value

View file

@ -90,7 +90,7 @@ def as_json():
def as_pdf():
response = Response()
response.mimetype = "application/pdf"
encoded_filename = quote(frappe.response['filename'].replace(' ', '_'), encoding='utf-8')
encoded_filename = quote(frappe.response['filename'].replace(' ', '_'))
response.headers["Content-Disposition"] = ("filename=\"%s\"" % frappe.response['filename'].replace(' ', '_') + ";filename*=utf-8''%s" % encoded_filename).encode("utf-8")
response.data = frappe.response['filecontent']
return response

View file

@ -337,3 +337,10 @@ def get_last_active():
WHERE `user_type` = 'System User' AND `name` NOT IN ({standard_users})"""
.format(standard_users=", ".join(["%s"]*len(STANDARD_USERS))),
STANDARD_USERS)[0][0]
@frappe.whitelist()
def activate_scheduler():
if is_scheduler_disabled():
enable_scheduler()
if frappe.conf.pause_scheduler:
update_site_config('pause_scheduler', 0)

View file

@ -161,7 +161,8 @@ class UserPermissions:
for docname in docs:
if frappe.get_meta(docname, cached=True).allow_import == 1:
self.can_import.append(docname)
frappe.cache().hset("can_import", frappe.session.user, self.can_import)
frappe.cache().hset("can_import", frappe.session.user, self.can_import)
def get_defaults(self):
import frappe.defaults

View file

@ -1,528 +1,158 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 1,
"allow_import": 1,
"allow_rename": 0,
"beta": 0,
"creation": "2013-03-28 10:35:30",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"title",
"published_on",
"published",
"column_break_3",
"blog_category",
"blogger",
"route",
"section_break_5",
"blog_intro",
"content_type",
"content",
"content_md",
"content_html",
"email_sent"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "published_on",
"fieldtype": "Date",
"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": "Published On",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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
"label": "Published On"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "published",
"fieldtype": "Check",
"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": "Published",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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
"label": "Published"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"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,
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "blog_category",
"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": 1,
"label": "Blog Category",
"length": 0,
"no_copy": 0,
"options": "Blog Category",
"permlevel": 0,
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "blogger",
"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": 1,
"label": "Blogger",
"length": 0,
"no_copy": 0,
"options": "Blogger",
"permlevel": 0,
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "route",
"fieldtype": "Data",
"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": "Route",
"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": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"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,
"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
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Description for listing page, in plain text, only a couple of lines. (max 140 characters)",
"fieldname": "blog_intro",
"fieldtype": "Small Text",
"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": "Blog Intro",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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
"label": "Blog Intro"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Rich Text",
"fieldname": "content_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": "Content Type",
"length": 0,
"no_copy": 0,
"options": "Rich Text\nMarkdown\nHTML",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.content_type === 'Rich Text'",
"fieldname": "content",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 1,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Content",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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
"label": "Content"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.content_type === 'Markdown'",
"fieldname": "content_md",
"fieldtype": "Markdown Editor",
"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": "Content (Markdown)",
"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
"label": "Content (Markdown)"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.content_type === 'HTML'",
"fieldname": "content_html",
"fieldtype": "HTML Editor",
"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": "Content (HTML)",
"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
"ignore_xss_filter": 1,
"label": "Content (HTML)"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "email_sent",
"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": "Email Sent",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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
"label": "Email Sent"
}
],
"has_web_view": 1,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-quote-left",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_published_field": "published",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2019-02-09 11:27:05.619819",
"modified": "2019-11-18 11:14:56.402471",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"set_user_permissions": 1,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Blogger",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"route": "/blog",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "title",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}

View file

@ -61,7 +61,7 @@ class BlogPost(WebsiteGenerator):
context.content = get_html_content_based_on_type(self, 'content', self.content_type)
context.description = self.blog_intro or context.content[:140]
context.description = self.blog_intro or strip_html_tags(context.content[:140])
context.metatags = {
"name": self.title,

View file

@ -132,12 +132,12 @@ $.extend(frappe, {
if (data._server_messages) {
var server_messages = JSON.parse(data._server_messages || '[]');
server_messages = $.map(server_messages, function(v) {
server_messages.map((msg) => {
// temp fix for messages sent as dict
try {
return JSON.parse(v).message;
return JSON.parse(msg);
} catch (e) {
return v;
return msg;
}
}).join('<br>');

View file

@ -9,6 +9,12 @@
{% block page_content %}
<!-- {{ for_test }} -->
<div style='min-height: 360px'>
<noscript>
<div class="text-center my-5">
<h4>{{ _("Javascript is disabled on your browser") }}</h4>
<p class="text-muted">{{ _("You need to enable JavaScript for your app to work.") }}<br>{{ _("To enable it follow the instructions in the following link: {0}").format("<a href='https://enable-javascript.com/'>enable-javascript.com</a></p>") }}
</div>
</noscript>
<section class='for-login'>
<div class="login-content page-card" style="margin-top: 30px;">
<form class="form-signin form-login" role="form">