Merge branch 'develop' into rpay-webhook-verification

This commit is contained in:
Suraj Shetty 2020-06-01 07:58:04 +05:30 committed by GitHub
commit 4e1ad550ec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 1303 additions and 663 deletions

View file

@ -25,6 +25,7 @@ cache:
# https://docs.cypress.io/guides/guides/continuous-integration.html#Caching
- ~/.cache
matrix:
include:
- name: "Python 3.7 MariaDB"
@ -46,7 +47,26 @@ matrix:
script: bench --site test_site run-ui-tests frappe --headless
before_install:
# install wkhtmltopdf
# do we really want to run travis?
- |
ONLY_DOCS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.(md|png|jpg|jpeg)$|^.github|LICENSE' ; echo $?)
ONLY_JS_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.js$' ; echo $?)
ONLY_PY_CHANGES=$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '\.py$' ; echo $?)
if [[ $ONLY_DOCS_CHANGES == "1" ]]; then
echo "Only docs were updated, stopping build process.";
exit;
fi
if [[ $ONLY_JS_CHANGES == "1" && $TYPE == "server" ]]; then
echo "Only JavaScript code was updated; Stopping Python build process.";
exit;
fi
if [[ $ONLY_PY_CHANGES == "1" && $TYPE == "ui" ]]; then
echo "Only Python code was updated, stopping Cypress build process.";
exit;
fi
# install wkhtmltopdf
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf

View file

@ -0,0 +1,45 @@
context('Control Duration', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
});
function get_dialog_with_duration(show_days=1, show_seconds=1) {
return cy.dialog({
title: 'Duration',
fields: [{
'fieldname': 'duration',
'fieldtype': 'Duration',
'show_seconds': show_days,
'show_days': show_seconds
}]
});
}
it('should set duration', () => {
get_dialog_with_duration().as('dialog');
cy.get('.frappe-control[data-fieldname=duration] input')
.first()
.click();
cy.get('.duration-input[data-duration=days]')
.type(45, {force: true})
.blur({force: true});
cy.get('.duration-input[data-duration=minutes]')
.type(30)
.blur({force: true});
cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m');
cy.get('.frappe-control[data-fieldname=duration] input').first().blur();
cy.get('.duration-picker').should('not.be.visible');
cy.get('@dialog').then(dialog => {
let value = dialog.get_value('duration');
expect(value).to.equal(3889800);
});
});
it('should hide days or seconds according to duration options', () => {
get_dialog_with_duration(0, 0).as('dialog');
cy.get('.frappe-control[data-fieldname=duration] input').first().click();
cy.get('.duration-input[data-duration=days]').should('not.be.visible');
cy.get('.duration-input[data-duration=seconds]').should('not.be.visible');
});
});

View file

@ -19,6 +19,7 @@ from frappe.email.inbox import get_email_accounts
from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled
from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
from frappe.model.base_document import get_controller
from frappe.social.doctype.post.post import frequently_visited_links
def get_bootinfo():
@ -106,6 +107,7 @@ def load_desktop_data(bootinfo):
from frappe.desk.desktop import get_desk_sidebar_items
bootinfo.allowed_modules = get_modules_from_all_apps_for_user()
bootinfo.allowed_workspaces = get_desk_sidebar_items(True)
bootinfo.module_page_map = get_controller("Desk Page").get_module_page_map()
bootinfo.dashboards = frappe.get_all("Dashboard")
def get_allowed_pages(cache=False):

View file

@ -43,14 +43,16 @@ def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin
_new_site(db_name, site, mariadb_root_username=mariadb_root_username,
mariadb_root_password=mariadb_root_password, admin_password=admin_password,
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force,
no_mariadb_socket=no_mariadb_socket, db_password=db_password, db_type=db_type, db_host=db_host, db_port=db_port)
no_mariadb_socket=no_mariadb_socket, db_password=db_password, db_type=db_type, db_host=db_host,
db_port=db_port, new_site=True)
if len(frappe.utils.get_sites()) == 1:
use(site)
def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None,
admin_password=None, verbose=False, install_apps=None, source_sql=None, force=False,
no_mariadb_socket=False, reinstall=False, db_password=None, db_type=None, db_host=None, db_port=None):
no_mariadb_socket=False, reinstall=False, db_password=None, db_type=None, db_host=None,
db_port=None, new_site=False):
"""Install a new Frappe site"""
if not force and os.path.exists(site):
@ -79,7 +81,10 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
make_site_dirs()
installing = touch_file(get_site_path('locks', 'installing.lock'))
atexit.register(_new_site_cleanup, site, mariadb_root_username, mariadb_root_password)
if new_site:
# run cleanup only if new-site is called
atexit.register(_new_site_cleanup, site, mariadb_root_username, mariadb_root_password)
install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
admin_password=admin_password, verbose=verbose, source_sql=source_sql, force=force, reinstall=reinstall,
@ -97,7 +102,10 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
print("*** Scheduler is", scheduler_status, "***")
def _new_site_cleanup(site, mariadb_root_username, mariadb_root_password):
installing = get_site_path('locks', 'installing.lock')
try:
installing = get_site_path('locks', 'installing.lock')
except AttributeError:
installing = os.path.join(site, 'locks', 'installing.lock')
if installing and os.path.exists(installing):
if mariadb_root_password:

View file

@ -2,20 +2,21 @@
# MIT License. See license.txt
from __future__ import unicode_literals, absolute_import
from collections import Counter
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import validate_email_address, get_fullname, strip_html, cstr
from frappe.core.doctype.communication.email import (validate_email,
notify, _notify, update_parent_mins_to_first_response)
from frappe.utils import validate_email_address, strip_html, cstr, time_diff_in_seconds
from frappe.core.doctype.communication.email import validate_email, notify, _notify
from frappe.core.utils import get_parent_doc
from frappe.utils.bot import BotReply
from frappe.utils import parse_addr
from frappe.core.doctype.comment.comment import update_comment_in_doc
from email.utils import parseaddr
from six.moves.urllib.parse import unquote
from collections import Counter
from frappe.utils.user import is_system_user
from frappe.contacts.doctype.contact.contact import get_contact_name
from frappe.automation.doctype.assignment_rule.assignment_rule import apply as apply_assignment_rule
exclude_from_linked_with = True
@ -119,7 +120,7 @@ class Communication(Document):
update_comment_in_doc(self)
if self.comment_type != 'Updated':
update_parent_mins_to_first_response(self)
update_parent_document_on_communication(self)
self.bot_reply()
def on_trash(self):
@ -258,7 +259,12 @@ class Communication(Document):
# Timeline Links
def set_timeline_links(self):
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
contacts = []
if (self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact")) or \
frappe.flags.in_test:
contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc])
for contact_name in contacts:
self.add_link('Contact', contact_name)
@ -423,3 +429,39 @@ def get_email_without_link(email):
email_host = email.split("@")[1]
return "{0}@{1}".format(email_id, email_host)
def update_parent_document_on_communication(doc):
"""Update mins_to_first_communication of parent document based on who is replying."""
parent = get_parent_doc(doc)
if not parent:
return
# update parent mins_to_first_communication only if we create the Email communication
# ignore in case of only Comment is added
if doc.communication_type == "Comment":
return
status_field = parent.meta.get_field("status")
if status_field:
options = (status_field.options or '').splitlines()
# if status has a "Replied" option, then update the status for received communication
if ('Replied' in options) and doc.sent_or_received=="Received":
parent.db_set("status", "Open")
apply_assignment_rule(parent)
else:
# update the modified date for document
parent.update_modified()
update_mins_to_first_communication(parent, doc)
parent.run_method('notify_communication', doc)
parent.notify_update()
def update_mins_to_first_communication(parent, communication):
if parent.meta.has_field('mins_to_first_response') and not parent.get('mins_to_first_response'):
if is_system_user(communication.sender):
first_responded_on = communication.creation
if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent":
parent.db_set('first_responded_on', first_responded_on)
parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)

View file

@ -9,7 +9,7 @@ import json
from email.utils import formataddr
from frappe.core.utils import get_parent_doc
from frappe.utils import (get_url, get_formatted_email, cint,
validate_email_address, split_emails, time_diff_in_seconds, parse_addr, get_datetime)
validate_email_address, split_emails, parse_addr, get_datetime)
from frappe.email.email_body import get_message_id
import frappe.email.smtp
import time
@ -172,33 +172,6 @@ def _notify(doc, print_html=None, print_format=None, attachments=None,
print_letterhead=frappe.flags.print_letterhead
)
def update_parent_mins_to_first_response(doc):
"""Update mins_to_first_communication of parent document based on who is replying."""
parent = get_parent_doc(doc)
if not parent:
return
# update parent mins_to_first_communication only if we create the Email communication
# ignore in case of only Comment is added
if doc.communication_type == "Comment":
return
status_field = parent.meta.get_field("status")
if status_field:
options = (status_field.options or '').splitlines()
# if status has a "Replied" option, then update the status for received communication
if ('Replied' in options) and doc.sent_or_received=="Received":
parent.db_set("status", "Open")
else:
# update the modified date for document
parent.update_modified()
update_mins_to_first_communication(parent, doc)
parent.run_method('notify_communication', doc)
parent.notify_update()
def get_recipients_cc_and_bcc(doc, recipients, cc, bcc, fetched_from_email_account=False):
doc.all_email_addresses = []
doc.sent_email_addresses = []
@ -499,15 +472,6 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments
traceback = frappe.log_error("frappe.core.doctype.communication.email.sendmail")
raise
def update_mins_to_first_communication(parent, communication):
if parent.meta.has_field('mins_to_first_response') and not parent.get('mins_to_first_response'):
if frappe.db.get_all('User', filters={'email': communication.sender,
'user_type': 'System User', 'enabled': 1}, limit=1):
first_responded_on = communication.creation
if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent":
parent.db_set('first_responded_on', first_responded_on)
parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)
@frappe.whitelist(allow_guest=True)
def mark_email_as_seen(name=None):
try:

View file

@ -202,6 +202,8 @@ class TestCommunication(unittest.TestCase):
self.assertIn(("Note", note.name), doc_links)
def create_email_account():
frappe.delete_doc_if_exists("Email Account", "_Test Comm Account 1")
frappe.flags.mute_emails = False
frappe.flags.sent_mail = None

View file

@ -13,6 +13,8 @@
"fieldname",
"precision",
"length",
"show_days",
"show_seconds",
"reqd",
"search_index",
"in_list_view",
@ -87,7 +89,7 @@
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"reqd": 1,
"search_index": 1
},
@ -450,6 +452,20 @@
"fieldname": "column_break_38",
"fieldtype": "Column Break"
},
{
"default": "1",
"depends_on": "eval:doc.fieldtype === \"Duration\";",
"fieldname": "show_days",
"fieldtype": "Check",
"label": "Show Days"
},
{
"default": "1",
"depends_on": "eval:doc.fieldtype === \"Duration\";",
"fieldname": "show_seconds",
"fieldtype": "Check",
"label": "Show Seconds"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
@ -461,7 +477,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-04-27 11:38:21.223185",
"modified": "2020-05-15 09:06:25.224411",
"modified_by": "Administrator",
"module": "Core",
"name": "DocField",

View file

@ -16,6 +16,8 @@
"column_break_6",
"fieldtype",
"precision",
"show_seconds",
"show_days",
"options",
"fetch_from",
"fetch_if_empty",
@ -56,368 +58,386 @@
],
"fields": [
{
"bold": 1,
"fieldname": "dt",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Document",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"search_index": 1
"bold": 1,
"fieldname": "dt",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Document",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"search_index": 1
},
{
"bold": 1,
"fieldname": "label",
"fieldtype": "Data",
"in_filter": 1,
"label": "Label",
"no_copy": 1,
"oldfieldname": "label",
"oldfieldtype": "Data"
"bold": 1,
"fieldname": "label",
"fieldtype": "Data",
"in_filter": 1,
"label": "Label",
"no_copy": 1,
"oldfieldname": "label",
"oldfieldtype": "Data"
},
{
"fieldname": "label_help",
"fieldtype": "HTML",
"label": "Label Help",
"oldfieldtype": "HTML"
"fieldname": "label_help",
"fieldtype": "HTML",
"label": "Label Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"no_copy": 1,
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"no_copy": 1,
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1
},
{
"description": "Select the label after which you want to insert new field.",
"fieldname": "insert_after",
"fieldtype": "Select",
"label": "Insert After",
"no_copy": 1,
"oldfieldname": "insert_after",
"oldfieldtype": "Select"
"description": "Select the label after which you want to insert new field.",
"fieldname": "insert_after",
"fieldtype": "Select",
"label": "Insert After",
"no_copy": 1,
"oldfieldname": "insert_after",
"oldfieldtype": "Select"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"bold": 1,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"label": "Field Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"reqd": 1
"bold": 1,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"label": "Field Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"reqd": 1
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
},
{
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
},
{
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
},
{
"fieldname": "options_help",
"fieldtype": "HTML",
"label": "Options Help",
"oldfieldtype": "HTML"
"fieldname": "options_help",
"fieldtype": "HTML",
"label": "Options Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
},
{
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On"
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On"
},
{
"fieldname": "default",
"fieldtype": "Text",
"label": "Default Value",
"oldfieldname": "default",
"oldfieldtype": "Text"
"fieldname": "default",
"fieldtype": "Text",
"label": "Default Value",
"oldfieldname": "default",
"oldfieldtype": "Text"
},
{
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"length": 255
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"length": 255
},
{
"fieldname": "description",
"fieldtype": "Text",
"label": "Field Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
"fieldname": "description",
"fieldtype": "Text",
"label": "Field Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
},
{
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"label": "Permission Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"label": "Permission Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
},
{
"fieldname": "width",
"fieldtype": "Data",
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data"
"fieldname": "width",
"fieldtype": "Data",
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data"
},
{
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
},
{
"fieldname": "properties",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
"width": "50%"
"fieldname": "properties",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
"width": "50%"
},
{
"default": "0",
"fieldname": "reqd",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory Field",
"oldfieldname": "reqd",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "reqd",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory Field",
"oldfieldname": "reqd",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
"default": "0",
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
},
{
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden"
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden"
},
{
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
},
{
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 1,
"label": "Print Width",
"no_copy": 1,
"print_hide": 1
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 1,
"label": "Print Width",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"fieldname": "no_copy",
"fieldtype": "Check",
"label": "No Copy",
"oldfieldname": "no_copy",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "no_copy",
"fieldtype": "Check",
"label": "No Copy",
"oldfieldname": "no_copy",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
},
{
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
},
{
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
},
{
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
},
{
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 1,
"label": "Index",
"no_copy": 1,
"print_hide": 1
"default": "0",
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 1,
"label": "Index",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
"label": "Ignore XSS Filter"
"default": "0",
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
"label": "Ignore XSS Filter"
},
{
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
},
{
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
},
{
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"length": 255
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"length": 255
},
{
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"length": 255
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"length": 255
},
{
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
},
{
"default": "0",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
"default": "0",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
"default": "1",
"depends_on": "eval:doc.fieldtype === \"Duration\";",
"fieldname": "show_seconds",
"fieldtype": "Check",
"label": "Show Seconds",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"depends_on": "eval:doc.fieldtype === \"Duration\";",
"fieldname": "show_days",
"fieldtype": "Check",
"label": "Show Days",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
}
],
"icon": "fa fa-glass",
"idx": 1,
"links": [],
"modified": "2020-04-27 11:40:48.325481",
"modified": "2020-05-15 23:43:00.123572",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Field",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "dt,label,fieldtype,options",

View file

@ -46,6 +46,9 @@ class CustomField(Document):
if not self.fieldname:
frappe.throw(_("Fieldname not set for Custom Field"))
if self.fieldname in fieldnames:
frappe.throw(_("A field with the name '{}' already exists in doctype {}.").format(self.fieldname, self.dt))
if self.get('translatable', 0) and not supports_translation(self.fieldtype):
self.translatable = 0

View file

@ -11,6 +11,8 @@
"label",
"fieldtype",
"fieldname",
"show_seconds",
"show_days",
"reqd",
"unique",
"in_list_view",
@ -58,350 +60,368 @@
],
"fields": [
{
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"label": "Label and Type"
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"label": "Label and Type"
},
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"oldfieldname": "label",
"oldfieldtype": "Data",
"search_index": 1
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"oldfieldname": "label",
"oldfieldtype": "Data",
"search_index": 1
},
{
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
"reqd": 1,
"search_index": 1
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Name",
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1,
"search_index": 1
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Name",
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
"fieldname": "reqd",
"fieldtype": "Check",
"label": "Mandatory",
"oldfieldname": "reqd",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
"default": "0",
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
"fieldname": "reqd",
"fieldtype": "Check",
"label": "Mandatory",
"oldfieldname": "reqd",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
},
{
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
},
{
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
},
{
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
},
{
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
},
{
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
},
{
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
},
{
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
},
{
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
},
{
"fieldname": "permissions",
"fieldtype": "Section Break",
"label": "Permissions"
"fieldname": "permissions",
"fieldtype": "Section Break",
"label": "Permissions"
},
{
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"oldfieldname": "depends_on",
"oldfieldtype": "Data",
"options": "JS"
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"oldfieldname": "depends_on",
"oldfieldtype": "Data",
"options": "JS"
},
{
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Perm Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Perm Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
},
{
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden",
"oldfieldname": "hidden",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden",
"oldfieldname": "hidden",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
},
{
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
},
{
"default": "0",
"depends_on": "eval: doc.fieldtype == \"Table\"",
"fieldname": "allow_bulk_edit",
"fieldtype": "Check",
"label": "Allow Bulk Edit"
"default": "0",
"depends_on": "eval: doc.fieldtype == \"Table\"",
"fieldname": "allow_bulk_edit",
"fieldtype": "Check",
"label": "Allow Bulk Edit"
},
{
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On",
"options": "JS"
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On",
"options": "JS"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
"default": "0",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
},
{
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:(doc.fieldtype == 'Link')",
"fieldname": "remember_last_selected_value",
"fieldtype": "Check",
"label": "Remember Last Selected Value"
"default": "0",
"depends_on": "eval:(doc.fieldtype == 'Link')",
"fieldname": "remember_last_selected_value",
"fieldtype": "Check",
"label": "Remember Last Selected Value"
},
{
"fieldname": "display",
"fieldtype": "Section Break",
"label": "Display"
"fieldname": "display",
"fieldtype": "Section Break",
"label": "Display"
},
{
"fieldname": "default",
"fieldtype": "Text",
"label": "Default",
"oldfieldname": "default",
"oldfieldtype": "Text"
"fieldname": "default",
"fieldtype": "Text",
"label": "Default",
"oldfieldname": "default",
"oldfieldtype": "Text"
},
{
"default": "0",
"fieldname": "in_filter",
"fieldtype": "Check",
"label": "In Filter",
"oldfieldname": "in_filter",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
"default": "0",
"fieldname": "in_filter",
"fieldtype": "Check",
"label": "In Filter",
"oldfieldname": "in_filter",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"fieldname": "description",
"fieldtype": "Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
"fieldname": "description",
"fieldtype": "Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
},
{
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
},
{
"description": "Print Width of the field, if the field is a column in a table",
"fieldname": "print_width",
"fieldtype": "Data",
"label": "Print Width",
"print_width": "50px",
"width": "50px"
"description": "Print Width of the field, if the field is a column in a table",
"fieldname": "print_width",
"fieldtype": "Data",
"label": "Print Width",
"print_width": "50px",
"width": "50px"
},
{
"depends_on": "eval:cur_frm.doc.istable",
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
"depends_on": "eval:cur_frm.doc.istable",
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
},
{
"fieldname": "width",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data",
"print_width": "50px",
"width": "50px"
"fieldname": "width",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data",
"print_width": "50px",
"width": "50px"
},
{
"default": "0",
"fieldname": "is_custom_field",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Custom Field",
"read_only": 1
"default": "0",
"fieldname": "is_custom_field",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Custom Field",
"read_only": 1
},
{
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
},
{
"fieldname": "property_depends_on_section",
"fieldtype": "Section Break",
"label": "Property Depends On"
"fieldname": "property_depends_on_section",
"fieldtype": "Section Break",
"label": "Property Depends On"
},
{
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"options": "JS"
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"options": "JS"
},
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
"fieldname": "column_break_33",
"fieldtype": "Column Break"
},
{
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"options": "JS"
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"options": "JS"
},
{
"default": "0",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
"default": "0",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
"default": "1",
"depends_on": "eval:doc.fieldtype === \"Duration\";",
"fieldname": "show_seconds",
"fieldtype": "Check",
"label": "Show Seconds",
"show_days": 1,
"show_seconds": 1
},
{
"default": "1",
"depends_on": "eval:doc.fieldtype === \"Duration\";",
"fieldname": "show_days",
"fieldtype": "Check",
"label": "Show Days",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-04-27 11:39:26.389300",
"modified": "2020-05-15 23:45:46.810869",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",

View file

@ -55,7 +55,8 @@ class MariaDBDatabase(Database):
'Signature': ('longtext', ''),
'Color': ('varchar', self.VARCHAR_LEN),
'Barcode': ('longtext', ''),
'Geolocation': ('longtext', '')
'Geolocation': ('longtext', ''),
'Duration': ('decimal', '18,6')
}
def get_connection(self):

View file

@ -60,7 +60,8 @@ class PostgresDatabase(Database):
'Signature': ('text', ''),
'Color': ('varchar', self.VARCHAR_LEN),
'Barcode': ('text', ''),
'Geolocation': ('text', '')
'Geolocation': ('text', ''),
'Duration': ('decimal', '18,6')
}
def get_connection(self):

View file

@ -251,6 +251,7 @@ frappe.ui.form.on('Dashboard Chart', {
render_filters_table: function(frm) {
frm.set_df_property("filters_section", "hidden", 0);
let is_document_type = frm.doc.chart_type!== 'Report' && frm.doc.chart_type!=='Custom';
let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default;
let wrapper = $(frm.get_field('filters_json').wrapper).empty();
let table = $(`<table class="table table-bordered" style="cursor:pointer; margin:0px;">
@ -268,6 +269,18 @@ frappe.ui.form.on('Dashboard Chart', {
let filters = JSON.parse(frm.doc.filters_json || '[]');
var filters_set = false;
// Set dynamic filters for reports
if (frm.doc.chart_type == 'Report') {
let set_filters = false;
frm.chart_filters.forEach(f => {
if (is_dynamic_filter(f)) {
filters[f.fieldname] = f.default;
set_filters = true;
}
});
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
}
let fields;
if (is_document_type) {
fields = [
@ -292,6 +305,7 @@ frappe.ui.form.on('Dashboard Chart', {
}
} else if (frm.chart_filters.length) {
fields = frm.chart_filters.filter(f => f.fieldname);
fields.map( f => {
if (filters[f.fieldname]) {
let condition = '=';
@ -318,7 +332,7 @@ frappe.ui.form.on('Dashboard Chart', {
let dialog = new frappe.ui.Dialog({
title: __('Set Filters'),
fields: fields,
fields: fields.filter(f => !is_dynamic_filter(f)),
primary_action: function() {
let values = this.get_values();
if (values) {
@ -351,8 +365,15 @@ frappe.ui.form.on('Dashboard Chart', {
}
dialog.show();
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
if (frm.doc.chart_type == 'Report') {
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
frappe.query_reports[frm.doc.report_name]
&& frappe.query_reports[frm.doc.report_name].onload
&& frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
}
dialog.set_values(filters);
});
},

View file

@ -20,6 +20,17 @@ class DeskPage(Document):
if frappe.conf.developer_mode and self.is_standard:
export_to_files(record_list=[['Desk Page', self.name]], record_module=self.module)
@staticmethod
def get_module_page_map():
filters = {
'extends_another_page': 0,
'for_user': '',
}
pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1)
return { page[1]: page[0] for page in pages }
def disable_saving_as_standard():
return frappe.flags.in_install or \
frappe.flags.in_patch or \

View file

@ -110,7 +110,11 @@ class UserProfile {
render_line_chart() {
this.line_chart_filters = [['Energy Point Log', 'user', '=', this.user_id, false]];
this.line_chart_filters = [
['Energy Point Log', 'user', '=', this.user_id, false],
['Energy Point Log', 'type', '!=', 'Review', false]
];
this.line_chart_config = {
timespan: 'Last Month',
time_interval: 'Daily',
@ -186,7 +190,10 @@ class UserProfile {
options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'],
action: (selected_item) => {
if (selected_item === 'All') {
if (this.line_chart_filters.length > 1) this.line_chart_filters.pop();
this.line_chart_filters = [
['Energy Point Log', 'user', '=', this.user_id, false],
['Energy Point Log', 'type', '!=', 'Review', false]
];
} else {
this.line_chart_filters[1] = ['Energy Point Log', 'type', '=', selected_item, false];
}

View file

@ -29,6 +29,7 @@
"default_incoming",
"email_sync_option",
"initial_sync_count",
"create_contact",
"section_break_12",
"enable_automatic_linking",
"section_break_13",
@ -114,9 +115,9 @@
"depends_on": "eval:!doc.service",
"fieldname": "domain",
"fieldtype": "Link",
"label": "Domain",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Domain",
"options": "Email Domain"
},
{
@ -408,11 +409,17 @@
"fieldname": "use_ssl_for_outgoing",
"fieldtype": "Check",
"label": "Use SSL for Outgoing"
},
{
"default": "1",
"fieldname": "create_contact",
"fieldtype": "Check",
"label": "Create Contacts from Incoming Emails"
}
],
"icon": "fa fa-inbox",
"links": [],
"modified": "2020-04-06 19:20:50.491146",
"modified": "2020-05-11 15:18:43.931499",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",
@ -427,11 +434,11 @@
"write": 1
},
{
"read": 1,
"role": "Inbox User"
"read": 1,
"role": "Inbox User"
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View file

@ -90,7 +90,7 @@ def backup_to_dropbox(upload_db_backup=True):
dropbox_settings['access_token'] = access_token['oauth2_token']
set_dropbox_access_token(access_token['oauth2_token'])
dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'])
dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'], timeout=None)
if upload_db_backup:
if frappe.flags.create_new_backup:

View file

@ -147,11 +147,14 @@ def sync_contacts_from_google_contacts(g_contact):
results = []
contacts_updated = 0
sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None
contacts = frappe._dict()
while True:
try:
sync_token = account.get_password(fieldname="next_sync_token", raise_exception=False) or None
contacts = google_contacts.people().connections().list(resourceName='people/me',syncToken=sync_token,
personFields="names,emailAddresses,organizations,phoneNumbers").execute()
contacts = google_contacts.people().connections().list(resourceName='people/me', pageToken=contacts.get("nextPageToken"),
syncToken=sync_token, pageSize=2000, requestSyncToken=True, personFields="names,emailAddresses,organizations,phoneNumbers").execute()
except HttpError as err:
frappe.throw(_("Google Contacts - Could not sync contacts from Google Contacts {0}, error code {1}.").format(account.name, err.resp.status))

View file

@ -34,7 +34,8 @@ data_fieldtypes = (
'Signature',
'Color',
'Barcode',
'Geolocation'
'Geolocation',
'Duration'
)
no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Table MultiSelect', 'Button', 'Image',

View file

@ -693,7 +693,7 @@ class BaseDocument(object):
df = self.meta.get_field(fieldname)
sanitized_value = value
if df and df.get("fieldtype") in ("Data", "Code", "Small Text") and df.get("options")=="Email":
if df and df.get("fieldtype") in ("Data", "Code", "Small Text", "Text") and df.get("options")=="Email":
sanitized_value = sanitize_email(value)
elif df and (df.get("ignore_xss_filter")

View file

@ -283,3 +283,6 @@ frappe.patches.v13_0.remove_tailwind_from_page_builder
frappe.patches.v13_0.rename_onboarding
frappe.patches.v13_0.email_unsubscribe
execute:frappe.delete_doc("Web Template", "Section with Left Image", force=1)
execute:frappe.delete_doc("DocType", "Onboarding Slide")
execute:frappe.delete_doc("DocType", "Onboarding Slide Field")
execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link")

View file

@ -38,6 +38,7 @@ import './table_multiselect';
import './multiselect_pills';
import './multiselect_list';
import './rating';
import './duration';
frappe.ui.form.make_control = function (opts) {
var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, "");

View file

@ -0,0 +1,152 @@
frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
make_input: function() {
this._super();
this.make_picker();
},
make_picker: function() {
this.inputs = [];
this.set_duration_options();
this.$picker = $(
`<div class="duration-picker">
<div class="picker-row row"></div>
</div>`
);
this.$wrapper.append(this.$picker);
this.build_numeric_input("days", !this.duration_options.show_days);
this.build_numeric_input("hours", false);
this.build_numeric_input("minutes", false);
this.build_numeric_input("seconds", !this.duration_options.show_seconds);
this.set_duration_picker_value(this.value);
this.$picker.hide();
this.bind_events();
this.refresh();
},
build_numeric_input: function(label, hidden, max) {
let $duration_input = $(`
<input class="input-sm duration-input" data-duration="${label}" type="number" min="0" value="0">
`);
let $input = $(`<div class="row duration-row"></div>`).prepend($duration_input);
if (max) {
$duration_input.attr("max", max);
}
this.inputs[label] = $duration_input;
let $control = $(`
<div class="col duration-col">
<div class="row duration-row duration-label">${__(label)}</div>
</div>`
);
if (hidden) {
$control.addClass("hidden");
}
$control.prepend($input);
$control.appendTo(this.$picker.find(".picker-row"));
},
set_duration_options() {
this.duration_options = frappe.utils.get_duration_options(this.df);
},
set_duration_picker_value: function(value) {
let total_duration = frappe.utils.seconds_to_duration(value, this.duration_options);
if (this.$picker) {
Object.keys(total_duration).forEach(duration => {
this.inputs[duration].prop("value", total_duration[duration]);
});
}
},
bind_events: function() {
// flag to handle the display property of the picker
let clicked = false;
this.$wrapper.find(".duration-input").mousedown(() => {
// input in individual duration boxes
clicked = true;
});
this.$picker.on("change", ".duration-input", () => {
// duration changed in individual boxes
clicked = false;
let duration = this.get_duration();
let value = frappe.utils.duration_to_seconds(
duration.days,
duration.hours,
duration.minutes,
duration.seconds
);
this.set_value(value);
this.set_focus();
});
this.$input.on("focus", () => {
this.$picker.show();
let is_picker_set = this.is_duration_picker_set(this.inputs);
if (!is_picker_set) {
this.set_duration_picker_value(this.value);
}
});
this.$input.on("blur", () => {
// input in duration boxes, don't close the picker
if (clicked) {
clicked = false;
} else {
// blur event was not due to duration inputs
this.$picker.hide();
}
});
},
get_value() {
return cint(this.value);
},
refresh_input: function() {
this._super();
this.set_duration_options();
this.set_duration_picker_value(this.value);
},
format_for_input: function(value) {
return frappe.utils.get_formatted_duration(value, this.duration_options);
},
get_duration() {
// returns an object of days, hours, minutes and seconds from the inputs array
let total_duration = {
minutes: 0,
hours: 0,
days: 0,
seconds: 0
};
if (this.inputs) {
total_duration.minutes = parseInt(this.inputs.minutes.val());
total_duration.hours = parseInt(this.inputs.hours.val());
if (this.duration_options.show_days) {
total_duration.days = parseInt(this.inputs.days.val());
}
if (this.duration_options.show_seconds) {
total_duration.seconds = parseInt(this.inputs.seconds.val());
}
}
return total_duration;
},
is_duration_picker_set(inputs) {
let is_set = false;
Object.values(inputs).forEach(duration => {
if (duration.prop("value") != 0) {
is_set = true;
}
});
return is_set;
}
});

View file

@ -589,7 +589,6 @@ frappe.ui.form.Timeline = class Timeline {
out.push(me.get_version_comment(version, message));
}
} else {
p = p.map(frappe.utils.escape_html);
const df = frappe.meta.get_docfield(me.frm.doctype, p[0], me.frm.docname);
if (df && !df.hidden) {
const field_display_status = frappe.perm.get_field_display_status(df, null,
@ -597,8 +596,8 @@ frappe.ui.form.Timeline = class Timeline {
if (field_display_status === 'Read' || field_display_status === 'Write') {
parts.push(__('{0} from {1} to {2}', [
__(df.label),
(frappe.ellipsis(frappe.utils.html2text(p[1]), 40) || '""').bold(),
(frappe.ellipsis(frappe.utils.html2text(p[2]), 40) || '""').bold()
me.format_content_for_timeline(p[1]),
me.format_content_for_timeline(p[2])
]));
}
}
@ -608,9 +607,9 @@ frappe.ui.form.Timeline = class Timeline {
if (parts.length) {
let message;
if (updater_reference_link) {
message = __("changed value of {0} {1}", [parts.join(', ').bold(), updater_reference_link]);
message = __("changed value of {0} {1}", [parts.join(', '), updater_reference_link]);
} else {
message = __("changed value of {0}", [parts.join(', ').bold()]);
message = __("changed value of {0}", [parts.join(', ')]);
}
out.push(me.get_version_comment(version, message));
}
@ -618,23 +617,23 @@ frappe.ui.form.Timeline = class Timeline {
// value changed in table field
if (data.row_changed && data.row_changed.length) {
var parts = [], count = 0;
let parts = [];
data.row_changed.every(function(row) {
row[3].every(function(p) {
var df = me.frm.fields_dict[row[0]] &&
frappe.meta.get_docfield(me.frm.fields_dict[row[0]].grid.doctype,
p[0], me.frm.docname);
if(df && !df.hidden) {
if (df && !df.hidden) {
var field_display_status = frappe.perm.get_field_display_status(df,
null, me.frm.perm);
if(field_display_status === 'Read' || field_display_status === 'Write') {
if (field_display_status === 'Read' || field_display_status === 'Write') {
parts.push(__('{0} from {1} to {2} in row #{3}', [
frappe.meta.get_label(me.frm.fields_dict[row[0]].grid.doctype,
p[0]),
(frappe.ellipsis(p[1], 40) || '""').bold(),
(frappe.ellipsis(p[2], 40) || '""').bold(),
me.format_content_for_timeline(p[1]),
me.format_content_for_timeline(p[2]),
row[1]
]));
}
@ -657,20 +656,22 @@ frappe.ui.form.Timeline = class Timeline {
// rows added / removed
// __('added'), __('removed') # for translation, don't remove
['added', 'removed'].forEach(function(key) {
if(data[key] && data[key].length) {
parts = (data[key] || []).map(function(p) {
if (data[key] && data[key].length) {
let parts = (data[key] || []).map(function(p) {
var df = frappe.meta.get_docfield(me.frm.doctype, p[0], me.frm.docname);
if(df && !df.hidden) {
if (df && !df.hidden) {
var field_display_status = frappe.perm.get_field_display_status(df, null,
me.frm.perm);
if(field_display_status === 'Read' || field_display_status === 'Write') {
if (field_display_status === 'Read' || field_display_status === 'Write') {
return frappe.meta.get_label(me.frm.doctype, p[0])
}
}
});
parts = parts.filter(function(p) { return p; });
if(parts.length) {
parts = parts.filter(function(p) {
return p;
});
if (parts.length) {
out.push(me.get_version_comment(version, __("{0} rows for {1}",
[__(key), parts.join(', ')])));
}
@ -717,6 +718,17 @@ frappe.ui.form.Timeline = class Timeline {
}
format_content_for_timeline(content) {
// text to HTML
// limits content to 40 characters
// escapes HTML
// and makes it bold
content = frappe.utils.html2text(content);
content = frappe.ellipsis(content, 40) || '""';
content = frappe.utils.escape_html(content);
return content.bold();
}
delete_comment(name) {
var me = this;

View file

@ -650,13 +650,14 @@ frappe.ui.form.Form = class FrappeForm {
frappe.utils.play_sound("submit");
callback && callback();
me.script_manager.trigger("on_submit")
.then(() => resolve(me));
if (frappe.route_hooks.after_submit) {
let route_callback = frappe.route_hooks.after_submit;
delete frappe.route_hooks.after_submit;
route_callback(me);
}
.then(() => resolve(me))
.then(() => {
if (frappe.route_hooks.after_submit) {
let route_callback = frappe.route_hooks.after_submit;
delete frappe.route_hooks.after_submit;
route_callback(me);
}
});
}
}, btn, () => me.handle_save_fail(btn, on_error), resolve);
});
@ -1586,7 +1587,7 @@ frappe.ui.form.Form = class FrappeForm {
let steps = frappe.tour[this.doctype].map(step => {
let field = this.get_docfield(step.fieldname);
return {
element: `.frappe-control[title='${step.fieldname}']`,
element: `.frappe-control[data-fieldname='${step.fieldname}']`,
popover: {
title: step.title || field.label,
description: step.description

View file

@ -142,10 +142,7 @@ frappe.form.formatters = {
},
DateRange: function(value) {
if($.isArray(value)) {
return __("{0} to {1}", [
frappe.datetime.str_to_user(value[0]),
frappe.datetime.str_to_user(value[1])
]);
return __("{0} to {1}", [frappe.datetime.str_to_user(value[0]), frappe.datetime.str_to_user(value[1])]);
} else {
return value || "";
}
@ -188,6 +185,14 @@ frappe.form.formatters = {
return value || "";
},
Duration: function(value, docfield) {
if (value) {
let duration_options = frappe.utils.get_duration_options(docfield);
value = frappe.utils.get_formatted_duration(value, duration_options);
}
return value || "";
},
LikedBy: function(value) {
var html = "";
$.each(JSON.parse(value || "[]"), function(i, v) {

View file

@ -16,12 +16,22 @@ frappe.ui.form.get_event_handler_list = function(doctype, fieldname) {
frappe.ui.form.on = frappe.ui.form.on_change = function(doctype, fieldname, handler) {
var add_handler = function(fieldname, handler) {
var handler_list = frappe.ui.form.get_event_handler_list(doctype, fieldname);
handler_list.push(handler);
let _handler = (...args) => {
try {
handler(...args);
} catch (error) {
console.error(handler);
throw error;
}
}
handler_list.push(_handler);
// add last handler to events so it can be called as
// frm.events.handler(frm)
if(cur_frm && cur_frm.doctype===doctype) {
cur_frm.events[fieldname] = handler;
cur_frm.events[fieldname] = _handler;
}
}

View file

@ -103,7 +103,11 @@ frappe.views.ListGroupBy = class ListGroupBy {
this.render_dropdown_items(field_count_list, fieldtype, dropdown);
frappe.utils.setup_search(dropdown, '.group-by-item', '.group-by-value', 'data-name');
} else {
dropdown.find('.group-by-loading').html(`${__("No filters found")}`);
dropdown.html(
`<div class="list-loading text-center group-by-empty text-muted">
${__("No filters found")}
</div>`
);
}
});
});

View file

@ -161,8 +161,7 @@ $.extend(frappe.meta, {
if(!out) {
// eslint-disable-next-line
console.log(__('Warning: Unable to find {0} in any table related to {1}', [
key, __(doctype)]));
console.log(__('Warning: Unable to find {0} in any table related to {1}', [key, __(doctype)]));
}
}
return out;
@ -266,5 +265,5 @@ $.extend(frappe.meta, {
precision = cint(frappe.defaults.get_default("float_precision")) || 3;
}
return precision;
},
}
});

View file

@ -200,6 +200,12 @@ frappe.ui.FilterList = Class.extend({
value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value;
} else if(field.df.original_type==="Check") {
value = {0:"No", 1:"Yes"}[cint(value)];
} else if (field.df.original_type === "Duration") {
let duration_options = {
show_days: field.df.show_days,
show_seconds: field.df.show_seconds
};
value = frappe.utils.get_formatted_duration(value, duration_options);
}
value = frappe.format(value, field.df, {only_value: 1});

View file

@ -803,6 +803,70 @@ Object.assign(frappe.utils, {
name: M[0],
version: M[1],
};
},
get_formatted_duration(value, duration_options) {
let duration = '';
if (value) {
let total_duration = frappe.utils.seconds_to_duration(value, duration_options);
if (total_duration.days) {
duration += total_duration.days + __('d', null, 'Days (Field: Duration)');
}
if (total_duration.hours) {
duration += (duration.length ? " " : "");
duration += total_duration.hours + __('h', null, 'Hours (Field: Duration)');
}
if (total_duration.minutes) {
duration += (duration.length ? " " : "");
duration += total_duration.minutes + __('m', null, 'Minutes (Field: Duration)');
}
if (total_duration.seconds) {
duration += (duration.length ? " " : "");
duration += total_duration.seconds + __('s', null, 'Seconds (Field: Duration)');
}
}
return duration;
},
seconds_to_duration(value, duration_options) {
let secs = value;
let total_duration = {
days: Math.floor(secs / (3600 * 24)),
hours: Math.floor(secs % (3600 * 24) / 3600),
minutes: Math.floor(secs % 3600 / 60),
seconds: Math.floor(secs % 60)
};
if (!duration_options.show_days) {
total_duration.hours = Math.floor(secs / 3600);
total_duration.days = 0;
}
return total_duration;
},
duration_to_seconds(days=0, hours=0, minutes=0, seconds=0) {
let value = 0;
if (days) {
value += days * 24 * 60 * 60;
}
if (hours) {
value += hours * 60 * 60;
}
if (minutes) {
value += minutes * 60;
}
if (seconds) {
value += seconds;
}
return value;
},
get_duration_options: function(docfield) {
let duration_options = {
show_days: docfield.show_days,
show_seconds: docfield.show_seconds
};
return duration_options;
}
});
@ -934,4 +998,4 @@ String.prototype.plural = function(revert) {
}
return this;
};
};

View file

@ -89,16 +89,21 @@ frappe.breadcrumbs = {
breadcrumbs.module = frappe.breadcrumbs.module_map[breadcrumbs.module];
}
if(frappe.get_module(breadcrumbs.module)) {
let current_module = breadcrumbs.module
// Check if a desk page exists
if (frappe.boot.module_page_map[breadcrumbs.module]) {
breadcrumbs.module = frappe.boot.module_page_map[breadcrumbs.module];
}
if(frappe.get_module(current_module)) {
// if module access exists
var module_info = frappe.get_module(breadcrumbs.module),
var module_info = frappe.get_module(current_module),
icon = module_info && module_info.icon,
label = module_info ? module_info.label : breadcrumbs.module;
if(module_info && !module_info.blocked && frappe.visible_modules.includes(module_info.module_name)) {
$(repl('<li><a href="#workspace/%(module)s">%(label)s</a></li>',
{ module: breadcrumbs.module, label: __(label) }))
{ module: breadcrumbs.module, label: __(breadcrumbs.module) }))
.appendTo($breadcrumbs);
}
}

View file

@ -97,7 +97,6 @@ export default class ChartWidget extends Widget {
this.chart_settings = {};
}
this.setup_container();
this.prepare_chart_object();
if (!this.in_customize_mode) {
this.action_area.empty();
this.prepare_chart_actions();
@ -110,7 +109,10 @@ export default class ChartWidget extends Widget {
this.render_time_series_filters();
}
}
this.fetch_and_update_chart();
frappe.run_serially([
() => this.prepare_chart_object(),
() => this.fetch_and_update_chart(),
]);
});
}
@ -412,6 +414,8 @@ export default class ChartWidget extends Widget {
dialog.show();
//Set query report object so that it can be used while fetching filter values in the report
frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
frappe.query_reports[this.chart_doc.report_name].onload
&& frappe.query_reports[this.chart_doc.report_name].onload(frappe.query_report);
dialog.set_values(this.filters);
}
@ -577,7 +581,7 @@ export default class ChartWidget extends Widget {
colors.push(field.color);
});
} else if (["Line", "Bar"].includes(this.chart_doc.type)) {
colors = [this.chart_doc.color || "light-blue"];
colors = [this.chart_doc.color || []];
} else if (this.chart_doc.type == "Heatmap") {
colors = [];
}
@ -623,13 +627,41 @@ export default class ChartWidget extends Widget {
}
prepare_chart_object() {
let saved_filters = this.chart_settings.filters || null;
this.filters =
saved_filters || this.filters || JSON.parse(this.chart_doc.filters_json || "[]");
if (this.chart_doc.type == 'Heatmap' && !this.chart_doc.heatmap_year) {
this.chart_doc.heatmap_year = frappe.dashboard_utils.get_year(frappe.datetime.now_date());
}
return this.set_chart_filters();
}
set_chart_filters() {
let user_saved_filters = this.chart_settings.filters || null;
let chart_saved_filters = JSON.parse(this.chart_doc.filters_json || "null");
if (this.chart_doc.chart_type == 'Report') {
return frappe.dashboard_utils
.get_filters_for_chart_type(this.chart_doc).then(filters => {
chart_saved_filters = this.update_default_date_filters(filters, chart_saved_filters);
this.filters =
user_saved_filters || this.filters || chart_saved_filters;
});
} else {
this.filters =
user_saved_filters || this.filters || chart_saved_filters;
return Promise.resolve();
}
}
update_default_date_filters(report_filters, chart_filters) {
report_filters.map(f => {
if (['Date', 'DateRange'].includes(f.fieldtype) && f.default) {
if (f.reqd || chart_filters[f.fieldname]) {
chart_filters[f.fieldname] = f.default;
}
}
});
return chart_filters;
}
get_settings() {

View file

@ -29,9 +29,13 @@ export default class ShortcutWidget extends Widget {
name: this.link_to,
type: this.type,
is_query_report: this.is_query_report,
doctype: this.ref_doctype
doctype: this.ref_doctype,
});
let filters = this.get_doctype_filter();
if (this.type == "DocType" && filters) {
frappe.route_options = filters;
}
frappe.set_route(route);
});
}
@ -40,16 +44,26 @@ export default class ShortcutWidget extends Widget {
if (this.in_customize_mode) return;
this.widget.addClass("shortcut-widget-box");
const get_filter = new Function(`return ${this.stats_filter}`);
if (this.type == "DocType" && this.stats_filter) {
let filters = this.get_doctype_filter();
if (this.type == "DocType" && filters) {
frappe.db
.count(this.link_to, {
filters: get_filter(),
filters: filters,
})
.then((count) => this.set_count(count));
}
}
get_doctype_filter() {
let count_filter = new Function(`return ${this.stats_filter}`)();
if (count_filter) {
return count_filter;
}
return null;
}
set_title() {
if (this.icon) {
this.title_field[0].innerHTML = `<div>
@ -82,4 +96,4 @@ export default class ShortcutWidget extends Widget {
buttons.appendTo(this.action_area);
}
}
}

View file

@ -241,11 +241,14 @@ class ShortcutDialog extends WidgetDialog {
if (this.dialog.get_value("type") == "DocType" && this.filter_group) {
let filters = this.filter_group.get_filters();
filters.forEach((arr) => {
stats_filter[arr[1]] = [arr[2], arr[3]];
});
data.stats_filter = JSON.stringify(stats_filter);
if (filters.length) {
filters.forEach((arr) => {
stats_filter[arr[1]] = [arr[2], arr[3]];
});
data.stats_filter = JSON.stringify(stats_filter);
}
}
data.label = data.label

View file

@ -165,3 +165,76 @@
pointer-events: none;
}
}
/* duration control */
.duration-picker {
position: absolute;
z-index: 999;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,.15);
background: #fff;
border: 1px solid @border-color;
padding-top: 10px;
padding-left: 5px;
&:after,
&:before {
border: solid transparent;
content: " ";
height: 0;
width: 0;
pointer-events: none;
position: absolute;
bottom: 100%;
left: 30px;
}
&:after {
border-color: rgba(255, 255, 255, 0);
border-bottom-color: #fff;
border-width: 8px;
margin-left: -8px;
}
&:before {
border-color: rgba(221, 221, 221, 0);
border-bottom-color: @border-color;
border-width: 9px;
margin-left: -9px;
}
.row .col {
// for fixing layout in child table
padding-left: 0px !important;
padding-right: 0px !important;
}
.duration-row {
margin: 7px;
display: flex;
}
.duration-col {
margin-left: 2px;
}
.duration-input {
width: 60px;
border: 1px solid rgba(0, 0, 0, 0.25) !important;
}
.duration-input:focus {
outline: None;
}
.duration-label {
justify-content: center;
}
.picker-row {
display: flex;
margin-left: 5px;
margin-bottom: 3px;
margin-right: 12px;
}
}

View file

@ -4,7 +4,7 @@
margin-top: 20px;
.desk-sidebar {
width: 24rem;
width: 20rem;
display: block;
position: fixed;
z-index: 1;
@ -69,7 +69,7 @@
}
.desk-body {
padding-left: calc(24rem + 15px);
padding-left: 20rem;
display: flex;
flex-direction: column;
height: 100%;

View file

@ -36,6 +36,7 @@
.dt-scrollable {
max-height: calc(100vh - 250px);
min-height: 100px;
scrollbar-width: thin;
}
table td.dt-cell {

View file

@ -33,7 +33,7 @@ def clear_sessions(user=None, keep_current=False, device=None, force=False):
:param user: user name (default: current user)
:param keep_current: keep current session (default: false)
:param device: delete sessions of this device (default: desktop)
:param device: delete sessions of this device (default: desktop, mobile)
:param force: triggered by the user (default false)
'''
@ -49,13 +49,16 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None):
:param user: user name (default: current user)
:param keep_current: keep current session (default: false)
:param device: delete sessions of this device (default: desktop)
:param device: delete sessions of this device (default: desktop, mobile)
'''
if not user:
user = frappe.session.user
if not device:
device = frappe.session.data.device or "desktop"
device = ("desktop", "mobile")
if not isinstance(device, (tuple, list)):
device = (device,)
offset = 0
if user == frappe.session.user:
@ -68,12 +71,12 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None):
return frappe.db.sql_list("""
SELECT `sid` FROM `tabSessions`
WHERE user=%s
AND device=%s
WHERE user=%(user)s
AND device in %(device)s
{condition}
ORDER BY `lastupdate` DESC
LIMIT 100 OFFSET {offset}""".format(condition=condition, offset=offset),
(user, device))
{"user": user, "device": device})
def delete_session(sid=None, user=None, reason="Session Expired"):
from frappe.core.doctype.activity_log.feed import logout_feed

View file

@ -145,6 +145,11 @@ table.no-border, table.no-border td {
margin: 3px 0px 3px;
}
.print-format table td pre {
white-space: normal;
word-break: normal;
}
table td div {
{% if not print_settings.allow_page_break_inside_tables %}
/* needed to avoid partial cutting of text between page break in wkhtmltopdf */

View file

@ -119,21 +119,19 @@ def get_dict(fortype, name=None):
messages += frappe.db.sql("select 'Role:', name from tabRole")
messages += frappe.db.sql("select 'Module:', name from `tabModule Def`")
message_dict = make_dict_from_messages(messages)
message_dict.update(get_dict_from_hooks(fortype, name))
# remove untranslated
message_dict = {k:v for k, v in iteritems(message_dict) if k!=v}
if fortype=="boot":
message_dict.update(get_user_translations(frappe.local.lang))
translation_assets[asset_key] = message_dict
cache.hset("translation_assets", frappe.local.lang, translation_assets, shared=True)
return translation_assets[asset_key]
translation_map = translation_assets[asset_key]
if fortype == "boot":
translation_map.update(get_user_translations(frappe.local.lang))
return translation_map
def get_dict_from_hooks(fortype, name):
translated_dict = {}

View file

@ -12,7 +12,9 @@ import frappe
from frappe import _, conf
from frappe.utils import cstr, get_url, now_datetime
_verbose = False
# backup variable for backwards compatibility
verbose = False
_verbose = verbose
class BackupGenerator:

View file

@ -89,10 +89,14 @@ def sync_dashboards(app=None):
config = get_config(app_name, module_name)
if config:
frappe.flags.in_import = True
make_records(config.charts, "Dashboard Chart")
make_records(config.number_cards, "Number Card")
make_records(config.dashboards, "Dashboard")
frappe.flags.in_import = False
try:
make_records(config.charts, "Dashboard Chart")
make_records(config.number_cards, "Number Card")
make_records(config.dashboards, "Dashboard")
except Exception as e:
frappe.log_error(e, _("Dashboard Import Error"))
finally:
frappe.flags.in_import = False
def make_records(config, doctype):
if not config:

View file

@ -323,6 +323,34 @@ def format_datetime(datetime_string, format_string=None):
formatted_datetime = datetime.strftime('%Y-%m-%d %H:%M:%S')
return formatted_datetime
def format_duration(seconds, show_days=True):
total_duration = {
'days': math.floor(seconds / (3600 * 24)),
'hours': math.floor(seconds % (3600 * 24) / 3600),
'minutes': math.floor(seconds % 3600 / 60),
'seconds': math.floor(seconds % 60)
}
if not show_days:
total_duration['hours'] = math.floor(seconds / 3600)
total_duration['days'] = 0
duration = ''
if total_duration:
if total_duration.get('days'):
duration += str(total_duration.get('days')) + 'd'
if total_duration.get('hours'):
duration += ' ' if len(duration) else ''
duration += str(total_duration.get('hours')) + 'h'
if total_duration.get('minutes'):
duration += ' ' if len(duration) else ''
duration += str(total_duration.get('minutes')) + 'm'
if total_duration.get('seconds'):
duration += ' ' if len(duration) else ''
duration += str(total_duration.get('seconds')) + 's'
return duration
def get_weekdays():
return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']

View file

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
import datetime
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time
from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration
from frappe.model.meta import get_field_currency, get_field_precision
import re
from six import string_types
@ -90,4 +90,8 @@ def format_value(value, df=None, doc=None, currency=None, translated=False):
values = [v.get(link_field.fieldname, 'asdf') for v in value]
return ', '.join(values)
elif df.get("fieldtype") == "Duration":
show_days = df.show_days
return format_duration(value, show_days)
return value

View file

@ -38,10 +38,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Website",
"modified": "2020-05-05 18:17:13.232473",
"modified": "2020-05-28 13:53:10.736212",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",
@ -51,7 +52,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"color": "",
"color": "#cef6d1",
"format": "{} Published",
"label": "Blog Post",
"link_to": "Blog Post",
@ -59,6 +60,7 @@
"type": "DocType"
},
{
"color": "#cef6d1",
"format": "{} Active",
"label": "Blogger",
"link_to": "Blogger",
@ -66,8 +68,11 @@
"type": "DocType"
},
{
"color": "#cef6d1",
"format": "{} Published",
"label": "Web Page",
"link_to": "Web Page",
"stats_filter": "{ \"published\": 1 }",
"type": "DocType"
},
{

View file

@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2014-09-01 14:14:14.292173",
"doctype": "DocType",
"editable_grid": 1,
@ -34,7 +35,7 @@
"fieldtype": "Select",
"in_list_view": 1,
"label": "Fieldtype",
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break"
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break"
},
{
"fieldname": "label",
@ -119,7 +120,8 @@
}
],
"istable": 1,
"modified": "2019-06-07 12:17:10.547133",
"links": [],
"modified": "2020-05-13 13:35:08.454427",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form Field",

View file

@ -10,7 +10,7 @@
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website",
"idx": 0,
"is_complete": 0,
"modified": "2020-05-13 12:32:35.269025",
"modified": "2020-05-28 13:51:57.535269",
"modified_by": "Administrator",
"module": "Website",
"name": "Website",

View file

@ -8,10 +8,12 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-04-30 19:06:10.750976",
"modified": "2020-05-28 13:51:53.157256",
"modified_by": "Administrator",
"name": "Add Blog Category",
"owner": "Administrator",
"reference_document": "Blog Category",
"title": "Add Blog Category"
"show_full_form": 0,
"title": "Add Blog Category",
"validate_action": 0
}

View file

@ -13,5 +13,7 @@
"name": "Create Blogger",
"owner": "Administrator",
"reference_document": "Blogger",
"title": "Create Blogger"
"show_full_form": 0,
"title": "Create Blogger",
"validate_action": 0
}

View file

@ -14,6 +14,8 @@
"name": "Enable Website Tracking",
"owner": "Administrator",
"reference_document": "Website Settings",
"show_full_form": 0,
"title": "Enable Website Tracking",
"validate_action": 0,
"value_to_validate": "1"
}

View file

@ -8,10 +8,12 @@
"is_mandatory": 1,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-04-30 19:06:10.578218",
"modified": "2020-05-28 13:51:51.485924",
"modified_by": "Administrator",
"name": "Introduction to Website",
"owner": "Administrator",
"show_full_form": 0,
"title": "Introduction to Website",
"validate_action": 0,
"video_url": "https://www.youtube.com/watch?v=lyW6mfFBSNw"
}

View file

@ -8,10 +8,12 @@
"is_mandatory": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2020-05-13 12:32:15.966570",
"modified": "2020-05-28 13:51:54.264456",
"modified_by": "Administrator",
"name": "Web Page Tour",
"owner": "Administrator",
"reference_document": "Web Page",
"title": "Learn about Web Pages"
"show_full_form": 0,
"title": "Learn about Web Pages",
"validate_action": 0
}

View file

@ -7,9 +7,9 @@
"custom_overrides": "",
"docstatus": 0,
"doctype": "Website Theme",
"font_properties": "wght:400;500;600;700;800",
"idx": 28,
"modified": "2020-05-19 00:33:58.011046",
"font_properties": "400,500,600,700,800",
"idx": 26,
"modified": "2020-05-20 14:47:12.938879",
"modified_by": "Administrator",
"module": "Website",
"name": "Standard",