Merge remote-tracking branch 'origin/develop' into ci/reports
Signed-off-by: mathieu.brunot <mathieu.brunot@monogramm.io>
This commit is contained in:
commit
d38e9855be
197 changed files with 3682 additions and 4750 deletions
2
.codacy.yml
Normal file
2
.codacy.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
exclude_paths:
|
||||
- '**.sql'
|
||||
2
.pylintrc
Normal file
2
.pylintrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
disable=access-member-before-definition
|
||||
disable=no-member
|
||||
40
.travis.yml
40
.travis.yml
|
|
@ -1,6 +1,5 @@
|
|||
language: python
|
||||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
|
|
@ -54,6 +53,16 @@ matrix:
|
|||
env: DB=mariadb TYPE=server
|
||||
script: bench --site test_site run-tests --coverage --junit-xml-output frappe_unit_tests.xml
|
||||
|
||||
before_install:
|
||||
# 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
|
||||
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
|
||||
|
||||
# install cups
|
||||
- sudo apt-get install libcups2-dev
|
||||
|
||||
install:
|
||||
- cd ~
|
||||
- source ./.nvm/nvm.sh
|
||||
|
|
@ -67,23 +76,20 @@ install:
|
|||
- mkdir ~/frappe-bench/sites/test_site
|
||||
- cp $TRAVIS_BUILD_DIR/.travis/$DB.json ~/frappe-bench/sites/test_site/site_config.json
|
||||
|
||||
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||
- if [ $DB == "mariadb" ];then
|
||||
mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'";
|
||||
mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
|
||||
mysql -u root -e "CREATE DATABASE test_frappe";
|
||||
mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'";
|
||||
mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'";
|
||||
mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'";
|
||||
mysql -u root -e "FLUSH PRIVILEGES";
|
||||
fi
|
||||
|
||||
- mysql -u root -e "CREATE DATABASE test_frappe"
|
||||
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||
|
||||
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
||||
- mysql -u root -e "FLUSH PRIVILEGES"
|
||||
|
||||
- psql -c "CREATE DATABASE test_frappe" -U postgres
|
||||
- psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres
|
||||
|
||||
- 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
|
||||
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
|
||||
- if [ $DB == "postgres" ];then
|
||||
psql -c "CREATE DATABASE test_frappe" -U postgres;
|
||||
psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres;
|
||||
fi
|
||||
|
||||
- cd ./frappe-bench
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
|
||||
});
|
||||
});
|
||||
it('create a new form', () => {
|
||||
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
|
||||
|
|
@ -13,4 +19,21 @@ context('Form', () => {
|
|||
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||
});
|
||||
it('navigates between documents with child table list filters applied', () => {
|
||||
cy.visit('/desk#List/Contact');
|
||||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true });
|
||||
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true });
|
||||
cy.get('.filter-box .btn:contains("Apply")').click({ force: true });
|
||||
cy.visit('/desk#Form/Contact/Test Form Contact 3');
|
||||
cy.get('.prev-doc').click();
|
||||
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
|
||||
cy.get('.modal-backdrop').click();
|
||||
cy.get('.next-doc').click();
|
||||
cy.contains('Test Form Contact 2').should('not.exist');
|
||||
cy.get('.page-title .title-text').should('contain', 'Test Form Contact 1');
|
||||
cy.visit('/desk#List/Contact');
|
||||
cy.get('.clear-filters.btn').click();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
51
cypress/integration/grid_pagination.js
Normal file
51
cypress/integration/grid_pagination.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
context('Grid Pagination', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
|
||||
});
|
||||
});
|
||||
it('creates pages for child table', () => {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.current-page-number').should('contain', '1');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
|
||||
});
|
||||
it('goes to the next and previous page', () => {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.next-page').click();
|
||||
cy.get('@table').find('.current-page-number').should('contain', '2');
|
||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '51');
|
||||
cy.get('@table').find('.prev-page').click();
|
||||
cy.get('@table').find('.current-page-number').should('contain', '1');
|
||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
|
||||
});
|
||||
it('adds and deletes rows and changes page', ()=> {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
|
||||
cy.get('@table').find('.current-page-number').should('contain', '21');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '21');
|
||||
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({force: true});
|
||||
cy.get('@table').find('button.grid-remove-rows').click();
|
||||
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000);
|
||||
cy.get('@table').find('.current-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
});
|
||||
it('deletes all rows', ()=> {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.grid-heading-row .grid-row-check').click({force: true});
|
||||
cy.get('@table').find('button.grid-remove-all-rows').click();
|
||||
cy.get('.modal-dialog .btn-primary').contains('Yes').click();
|
||||
cy.get('@table').find('.grid-body .grid-row').should('have.length', 0);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,10 +2,9 @@ context('List View', () => {
|
|||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.setup_workflow");
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
|
||||
});
|
||||
cy.clear_cache();
|
||||
});
|
||||
it('enables "Actions" button', () => {
|
||||
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Print', 'Delete'];
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ if sys.version[0] == '2':
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '12.0.17'
|
||||
__version__ = '12.0.20'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
@ -123,7 +123,6 @@ def init(site, sites_path=None, new_site=False):
|
|||
local.debug_log = []
|
||||
local.realtime_log = []
|
||||
local.flags = _dict({
|
||||
"ran_schedulers": [],
|
||||
"currently_saving": [],
|
||||
"redirect_location": "",
|
||||
"in_install_db": False,
|
||||
|
|
@ -1508,7 +1507,22 @@ def logger(module=None, with_more_info=True):
|
|||
|
||||
def log_error(message=None, title=None):
|
||||
'''Log error to Error Log'''
|
||||
return get_doc(dict(doctype='Error Log', error=as_unicode(message or get_traceback()),
|
||||
|
||||
# AI ALERT:
|
||||
# the title and message may be swapped
|
||||
# the better API for this is log_error(title, message), and used in many cases this way
|
||||
# this hack tries to be smart about whats a title (single line ;-)) and fixes it
|
||||
|
||||
if message:
|
||||
if '\n' not in message:
|
||||
title = message
|
||||
error = get_traceback()
|
||||
else:
|
||||
error = message
|
||||
else:
|
||||
error = get_traceback()
|
||||
|
||||
return get_doc(dict(doctype='Error Log', error=as_unicode(error),
|
||||
method=title)).insert(ignore_permissions=True)
|
||||
|
||||
def get_desk_link(doctype, name):
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ def handle():
|
|||
|
||||
if frappe.local.request.method=="PUT":
|
||||
if frappe.local.form_dict.data is None:
|
||||
data = json.loads(frappe.local.request.get_data())
|
||||
data = json.loads(frappe.safe_decode(frappe.local.request.get_data()))
|
||||
else:
|
||||
data = json.loads(frappe.local.form_dict.data)
|
||||
doc = frappe.get_doc(doctype, name)
|
||||
|
|
@ -117,7 +117,7 @@ def handle():
|
|||
|
||||
if frappe.local.request.method=="POST":
|
||||
if frappe.local.form_dict.data is None:
|
||||
data = json.loads(frappe.local.request.get_data())
|
||||
data = json.loads(frappe.safe_decode(frappe.local.request.get_data()))
|
||||
else:
|
||||
data = json.loads(frappe.local.form_dict.data)
|
||||
data.update({
|
||||
|
|
|
|||
|
|
@ -105,10 +105,6 @@ class AutoRepeat(Document):
|
|||
schedule_details = []
|
||||
start_date = getdate(self.start_date)
|
||||
end_date = getdate(self.end_date)
|
||||
today = frappe.utils.datetime.date.today()
|
||||
|
||||
if start_date < today:
|
||||
start_date = today
|
||||
|
||||
if not self.end_date:
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
|
|
@ -121,7 +117,8 @@ class AutoRepeat(Document):
|
|||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
|
||||
if self.end_date:
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
start_date = start_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, end_date, for_full_schedule=True)
|
||||
while (getdate(start_date) < getdate(end_date)):
|
||||
row = {
|
||||
"reference_document" : self.reference_document,
|
||||
|
|
@ -129,7 +126,8 @@ class AutoRepeat(Document):
|
|||
"next_scheduled_date" : start_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, end_date)
|
||||
start_date = start_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, end_date, for_full_schedule=True)
|
||||
|
||||
|
||||
return schedule_details
|
||||
|
|
@ -271,18 +269,28 @@ class AutoRepeat(Document):
|
|||
)
|
||||
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day, repeat_on_last_day = False, end_date = None):
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day, repeat_on_last_day=False, end_date=None, for_full_schedule=False):
|
||||
month_count = month_map.get(frequency)
|
||||
day_count = 0
|
||||
if month_count and repeat_on_last_day:
|
||||
next_date = get_next_date(start_date, month_count, 31)
|
||||
day_count = 31
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif month_count and repeat_on_day:
|
||||
next_date = get_next_date(start_date, month_count, repeat_on_day)
|
||||
day_count = repeat_on_day
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif month_count:
|
||||
next_date = get_next_date(start_date, month_count)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(start_date, days)
|
||||
|
||||
# next schedule date should be after or on current date
|
||||
if not for_full_schedule:
|
||||
while getdate(next_date) < getdate(today()):
|
||||
next_date = get_next_date(next_date, month_count, day_count)
|
||||
|
||||
return next_date
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
|
|
@ -307,7 +315,7 @@ def create_repeated_entries(data):
|
|||
current_date = getdate(today())
|
||||
schedule_date = getdate(doc.next_schedule_date)
|
||||
|
||||
while schedule_date <= current_date and not doc.disabled:
|
||||
if schedule_date == current_date and not doc.disabled:
|
||||
doc.create_documents()
|
||||
schedule_date = get_next_schedule_date(schedule_date, doc.frequency, doc.repeat_on_day, doc.repeat_on_last_day, doc.end_date)
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,21 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
linked_comm = frappe.db.exists("Communication", dict(reference_doctype="ToDo", reference_name=new_todo))
|
||||
self.assertTrue(linked_comm)
|
||||
|
||||
def test_next_schedule_date(self):
|
||||
current_date = getdate(today())
|
||||
todo = frappe.get_doc(
|
||||
dict(doctype='ToDo', description='test next schedule date todo', assigned_by='Administrator')).insert()
|
||||
doc = make_auto_repeat(frequency='Monthly', reference_document=todo.name, start_date=add_months(today(), -2))
|
||||
|
||||
#check next_schedule_date is set as per current date
|
||||
#it should not be a previous month's date
|
||||
self.assertEqual(doc.next_schedule_date, current_date)
|
||||
data = get_auto_repeat_entries(current_date)
|
||||
create_repeated_entries(data)
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name})
|
||||
#the original doc + the repeated doc
|
||||
self.assertEqual(len(docnames), 2)
|
||||
|
||||
|
||||
def make_auto_repeat(**args):
|
||||
args = frappe._dict(args)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ from six import text_type
|
|||
@click.argument('site')
|
||||
@click.option('--db-name', help='Database name')
|
||||
@click.option('--db-type', default='mariadb', type=click.Choice(['mariadb', 'postgres']), help='Optional "postgres" or "mariadb". Default is "mariadb"')
|
||||
@click.option('--db-host', help='Database Host')
|
||||
@click.option('--db-port', type=int, help='Database Port')
|
||||
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
|
||||
@click.option('--mariadb-root-password', help='Root password for MariaDB')
|
||||
@click.option('--admin-password', help='Administrator password for new site', default=None)
|
||||
|
|
@ -21,22 +23,22 @@ from six import text_type
|
|||
@click.option('--source_sql', help='Initiate database with a SQL file')
|
||||
@click.option('--install-app', multiple=True, help='Install app after installation')
|
||||
def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None,
|
||||
verbose=False, install_apps=None, source_sql=None, force=None, install_app=None,
|
||||
db_name=None, db_type=None):
|
||||
verbose=False, install_apps=None, source_sql=None, force=None, install_app=None,
|
||||
db_name=None, db_type=None, db_host=None, db_port=None):
|
||||
"Create a new site"
|
||||
frappe.init(site=site, new_site=True)
|
||||
|
||||
_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,
|
||||
db_type=db_type)
|
||||
mariadb_root_password=mariadb_root_password, admin_password=admin_password,
|
||||
verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force,
|
||||
db_type=db_type, db_host=db_host, db_port=db_port)
|
||||
|
||||
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,
|
||||
reinstall=False, db_type=None):
|
||||
admin_password=None, verbose=False, install_apps=None, source_sql=None, force=False,
|
||||
reinstall=False, db_type=None, db_host=None, db_port=None):
|
||||
"""Install a new Frappe site"""
|
||||
|
||||
if not force and os.path.exists(site):
|
||||
|
|
@ -65,8 +67,8 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
|
|||
installing = touch_file(get_site_path('locks', 'installing.lock'))
|
||||
|
||||
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, db_type=db_type)
|
||||
db_name=db_name, admin_password=admin_password, verbose=verbose,
|
||||
source_sql=source_sql, force=force, reinstall=reinstall, db_type=db_type, db_host=db_host, db_port=db_port)
|
||||
|
||||
apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
|
||||
for app in apps_to_install:
|
||||
|
|
|
|||
|
|
@ -507,26 +507,6 @@ def run_ui_tests(context, app, headless=False):
|
|||
formatted_command = command.format(site_env=site_env, password_env=password_env, run_or_open=run_or_open)
|
||||
frappe.commands.popen(formatted_command, cwd=app_base_path, raise_err=True)
|
||||
|
||||
@click.command('run-setup-wizard-ui-test')
|
||||
@click.option('--app', help="App to run tests on, leave blank for all apps")
|
||||
@click.option('--profile', is_flag=True, default=False)
|
||||
@pass_context
|
||||
def run_setup_wizard_ui_test(context, app=None, profile=False):
|
||||
"Run setup wizard UI test"
|
||||
import frappe.test_runner
|
||||
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
||||
ret = frappe.test_runner.run_setup_wizard_ui_test(app=app, verbose=context.verbose,
|
||||
profile=profile)
|
||||
if len(ret.failures) == 0 and len(ret.errors) == 0:
|
||||
ret = 0
|
||||
|
||||
if os.environ.get('CI'):
|
||||
sys.exit(ret)
|
||||
|
||||
@click.command('serve')
|
||||
@click.option('--port', default=8000)
|
||||
@click.option('--profile', is_flag=True, default=False)
|
||||
|
|
@ -752,7 +732,6 @@ commands = [
|
|||
reset_perms,
|
||||
run_tests,
|
||||
run_ui_tests,
|
||||
run_setup_wizard_ui_test,
|
||||
serve,
|
||||
set_config,
|
||||
show_config,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.moduleview import add_setup_section
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ def get_data():
|
|||
]
|
||||
},
|
||||
{
|
||||
"label": _("Email"),
|
||||
"label": _("Email / Notifications"),
|
||||
"icon": "fa fa-envelope",
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -120,6 +121,12 @@ def get_data():
|
|||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"description": _("Create and manage newsletter")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"route": "Form/Notification Settings/{}".format(frappe.session.user),
|
||||
"name": "Notification Settings",
|
||||
"description": _("Configure notifications for mentions, assignments, energy points and more.")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -60,10 +60,6 @@ class Address(Document):
|
|||
if not [row for row in self.links if row.link_doctype == "Company"]:
|
||||
frappe.throw(_("Company is mandatory, as it is your company address"))
|
||||
|
||||
# removing other links
|
||||
to_remove = [row for row in self.links if row.link_doctype != "Company"]
|
||||
[ self.remove(row) for row in to_remove ]
|
||||
|
||||
def get_display(self):
|
||||
return get_address_display(self.as_dict())
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ 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)
|
||||
from frappe.utils.scheduler import log
|
||||
from frappe.email.email_body import get_message_id
|
||||
import frappe.email.smtp
|
||||
import time
|
||||
|
|
@ -399,7 +398,7 @@ def get_bcc(doc, recipients=None, fetched_from_email_account=False):
|
|||
return bcc
|
||||
|
||||
def add_attachments(name, attachments):
|
||||
'''Add attachments to the given Communiction'''
|
||||
'''Add attachments to the given Communication'''
|
||||
# loop through attachments
|
||||
for a in attachments:
|
||||
if isinstance(a, string_types):
|
||||
|
|
@ -412,7 +411,9 @@ def add_attachments(name, attachments):
|
|||
"file_url": attach.file_url,
|
||||
"attached_to_doctype": "Communication",
|
||||
"attached_to_name": name,
|
||||
"folder": "Home/Attachments"})
|
||||
"folder": "Home/Attachments",
|
||||
"is_private": attach.is_private
|
||||
})
|
||||
_file.save(ignore_permissions=True)
|
||||
|
||||
def filter_email_list(doc, email_list, exclude, is_cc=False, is_bcc=False):
|
||||
|
|
@ -509,17 +510,7 @@ def sendmail(communication_name, print_html=None, print_format=None, attachments
|
|||
break
|
||||
|
||||
except:
|
||||
traceback = log("frappe.core.doctype.communication.email.sendmail", frappe.as_json({
|
||||
"communication_name": communication_name,
|
||||
"print_html": print_html,
|
||||
"print_format": print_format,
|
||||
"attachments": attachments,
|
||||
"recipients": recipients,
|
||||
"cc": cc,
|
||||
"bcc": bcc,
|
||||
"lang": lang
|
||||
}))
|
||||
frappe.logger(__name__).error(traceback)
|
||||
traceback = frappe.log_error("frappe.core.doctype.communication.email.sendmail")
|
||||
raise
|
||||
|
||||
def update_mins_to_first_communication(parent, communication):
|
||||
|
|
|
|||
|
|
@ -54,8 +54,10 @@ class Importer:
|
|||
extension = None
|
||||
if self.data_import and self.data_import.import_file:
|
||||
file_doc = frappe.get_doc("File", {"file_url": self.data_import.import_file})
|
||||
parts = file_doc.get_extension()
|
||||
extension = parts[1]
|
||||
content = file_doc.get_content()
|
||||
extension = file_doc.file_name.split(".")[1]
|
||||
extension = extension.lstrip(".")
|
||||
|
||||
if file_path:
|
||||
content, extension = self.read_file(file_path)
|
||||
|
|
@ -79,6 +81,12 @@ class Importer:
|
|||
return file_content, extn
|
||||
|
||||
def read_content(self, content, extension):
|
||||
error_title = _("Template Error")
|
||||
if extension not in ("csv", "xlsx", "xls"):
|
||||
frappe.throw(
|
||||
_("Import template should be of type .csv, .xlsx or .xls"), title=error_title
|
||||
)
|
||||
|
||||
if extension == "csv":
|
||||
data = read_csv_content(content)
|
||||
elif extension == "xlsx":
|
||||
|
|
@ -86,6 +94,11 @@ class Importer:
|
|||
elif extension == "xls":
|
||||
data = read_xls_file_from_attached_file(content)
|
||||
|
||||
if len(data) <= 1:
|
||||
frappe.throw(
|
||||
_("Import template should contain a Header and atleast one row."), title=error_title
|
||||
)
|
||||
|
||||
self.header_row = data[0]
|
||||
self.data = data[1:]
|
||||
|
||||
|
|
@ -862,15 +875,15 @@ class Importer:
|
|||
|
||||
if failed_records:
|
||||
print("Failed to import {0} records".format(len(failed_records)))
|
||||
file_name = '{0}_import_on_{1}.txt'.format(self.doctype, frappe.utils.now())
|
||||
print('Check {0} for errors'.format(os.path.join('sites', file_name)))
|
||||
file_name = "{0}_import_on_{1}.txt".format(self.doctype, frappe.utils.now())
|
||||
print("Check {0} for errors".format(os.path.join("sites", file_name)))
|
||||
text = ""
|
||||
for w in failed_records:
|
||||
text += "Row Indexes: {0}\n".format(str(w.get('row_indexes', [])))
|
||||
text += "Messages:\n{0}\n".format('\n'.join(w.get('messages', [])))
|
||||
text += "Traceback:\n{0}\n\n".format(w.get('exception'))
|
||||
text += "Row Indexes: {0}\n".format(str(w.get("row_indexes", [])))
|
||||
text += "Messages:\n{0}\n".format("\n".join(w.get("messages", [])))
|
||||
text += "Traceback:\n{0}\n\n".format(w.get("exception"))
|
||||
|
||||
with open(file_name, 'w') as f:
|
||||
with open(file_name, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2013-02-18 13:36:19",
|
||||
|
|
@ -28,6 +29,7 @@
|
|||
"name_case",
|
||||
"column_break_15",
|
||||
"description",
|
||||
"documentation",
|
||||
"form_settings_section",
|
||||
"image_field",
|
||||
"timeline_field",
|
||||
|
|
@ -57,6 +59,10 @@
|
|||
"restrict_to_domain",
|
||||
"read_only",
|
||||
"in_create",
|
||||
"actions_section",
|
||||
"actions",
|
||||
"links_section",
|
||||
"links",
|
||||
"web_view",
|
||||
"has_web_view",
|
||||
"allow_guest_to_view",
|
||||
|
|
@ -454,11 +460,39 @@
|
|||
"fieldname": "nsm_parent_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "Parent Field (Tree)"
|
||||
},
|
||||
{
|
||||
"description": "URL for documentation or help",
|
||||
"fieldname": "documentation",
|
||||
"fieldtype": "Data",
|
||||
"label": "Documentation Link"
|
||||
},
|
||||
{
|
||||
"fieldname": "actions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Actions"
|
||||
},
|
||||
{
|
||||
"fieldname": "actions",
|
||||
"fieldtype": "Table",
|
||||
"label": "Actions",
|
||||
"options": "DocType Action"
|
||||
},
|
||||
{
|
||||
"fieldname": "links_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Links Section"
|
||||
},
|
||||
{
|
||||
"fieldname": "links",
|
||||
"fieldtype": "Table",
|
||||
"label": "Links",
|
||||
"options": "DocType Link"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
"idx": 6,
|
||||
"modified": "2019-09-07 14:28:05.392490",
|
||||
"modified": "2019-11-25 17:24:03.690192",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
|
|||
57
frappe/core/doctype/doctype_action/doctype_action.json
Normal file
57
frappe/core/doctype/doctype_action/doctype_action.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2019-09-23 16:28:13.953520",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"label",
|
||||
"action_type",
|
||||
"action",
|
||||
"group"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "group",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Group"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "action_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Action Type",
|
||||
"options": "Server Action",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"fieldname": "action",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Action",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-09-24 09:11:39.860100",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType Action",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/doctype_action/doctype_action.py
Normal file
10
frappe/core/doctype/doctype_action/doctype_action.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DocTypeAction(Document):
|
||||
pass
|
||||
46
frappe/core/doctype/doctype_link/doctype_link.json
Normal file
46
frappe/core/doctype/doctype_link/doctype_link.json
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2019-09-24 11:41:25.291377",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"link_doctype",
|
||||
"link_fieldname",
|
||||
"group"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "link_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Link DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "link_fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Link Fieldname",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "group",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Group"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-09-24 11:41:25.291377",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType Link",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/doctype_link/doctype_link.py
Normal file
10
frappe/core/doctype/doctype_link/doctype_link.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DocTypeLink(Document):
|
||||
pass
|
||||
|
|
@ -89,8 +89,9 @@ class File(Document):
|
|||
|
||||
def validate(self):
|
||||
if self.is_new():
|
||||
self.set_is_private()
|
||||
self.set_file_name()
|
||||
self.validate_duplicate_entry()
|
||||
self.validate_file_name()
|
||||
self.validate_folder()
|
||||
|
||||
if not self.file_url and not self.flags.ignore_file_validate:
|
||||
|
|
@ -133,6 +134,9 @@ class File(Document):
|
|||
frappe.db.set_value(self.attached_to_doctype, self.attached_to_name,
|
||||
self.attached_to_field, self.file_url)
|
||||
|
||||
if self.file_url and (self.is_private != self.file_url.startswith('/private')):
|
||||
frappe.throw(_('Invalid file URL. Please contact System Administrator.'))
|
||||
|
||||
def set_folder_name(self):
|
||||
"""Make parent folders if not exists based on reference doctype and name"""
|
||||
if self.attached_to_doctype and not self.folder:
|
||||
|
|
@ -157,9 +161,11 @@ class File(Document):
|
|||
|
||||
def validate_duplicate_entry(self):
|
||||
if not self.flags.ignore_duplicate_entry_error and not self.is_folder:
|
||||
# check duplicate name
|
||||
if not self.content_hash:
|
||||
self.generate_content_hash()
|
||||
|
||||
# check duplicate assignement
|
||||
# check duplicate name
|
||||
# check duplicate assignment
|
||||
filters = {
|
||||
'content_hash': self.content_hash,
|
||||
'is_private': self.is_private,
|
||||
|
|
@ -184,21 +190,20 @@ class File(Document):
|
|||
else:
|
||||
self.file_url = duplicate_file.file_url
|
||||
|
||||
def validate_file_name(self):
|
||||
def set_file_name(self):
|
||||
if not self.file_name and self.file_url:
|
||||
self.file_name = self.file_url.split('/')[-1]
|
||||
|
||||
def generate_content_hash(self):
|
||||
if self.content_hash or not self.file_url:
|
||||
if self.content_hash or not self.file_url or self.file_url.startswith('http'):
|
||||
return
|
||||
|
||||
if self.file_url.startswith("/files/"):
|
||||
try:
|
||||
with open(get_files_path(self.file_name.lstrip("/")), "rb") as f:
|
||||
self.content_hash = get_content_hash(f.read())
|
||||
except IOError:
|
||||
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
|
||||
raise
|
||||
try:
|
||||
with open(get_files_path(self.file_name.lstrip("/"), is_private=self.is_private), "rb") as f:
|
||||
self.content_hash = get_content_hash(f.read())
|
||||
except IOError:
|
||||
frappe.msgprint(_("File {0} does not exist").format(self.file_url))
|
||||
raise
|
||||
|
||||
def on_trash(self):
|
||||
if self.is_home_folder or self.is_attachments_folder:
|
||||
|
|
@ -563,6 +568,9 @@ class File(Document):
|
|||
except frappe.DoesNotExistError:
|
||||
frappe.clear_messages()
|
||||
|
||||
def set_is_private(self):
|
||||
if self.file_url:
|
||||
self.is_private = cint(self.file_url.startswith('/private'))
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("File", ["attached_to_doctype", "attached_to_name"])
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class PreparedReport(Document):
|
|||
enqueue(run_background, prepared_report=self.name, timeout=6000)
|
||||
|
||||
def on_trash(self):
|
||||
remove_all("PreparedReport", self.name, from_delete=True)
|
||||
remove_all("Prepared Report", self.name)
|
||||
|
||||
|
||||
def run_background(prepared_report):
|
||||
|
|
@ -85,7 +85,8 @@ def create_json_gz_file(data, dt, dn):
|
|||
"file_name": json_filename,
|
||||
"attached_to_doctype": dt,
|
||||
"attached_to_name": dn,
|
||||
"content": compressed_content
|
||||
"content": compressed_content,
|
||||
"is_private": 1
|
||||
})
|
||||
_file.save(ignore_permissions=True)
|
||||
|
||||
|
|
|
|||
0
frappe/core/doctype/scheduled_job_log/__init__.py
Normal file
0
frappe/core/doctype/scheduled_job_log/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Scheduled Job Log', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
64
frappe/core/doctype/scheduled_job_log/scheduled_job_log.json
Normal file
64
frappe/core/doctype/scheduled_job_log/scheduled_job_log.json
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2019-09-23 14:36:36.935869",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"status",
|
||||
"scheduled_job_type",
|
||||
"details"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"options": "Scheduled\nSuccess\nFailed",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "details",
|
||||
"fieldtype": "Code",
|
||||
"label": "Details",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "scheduled_job_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Scheduled Job",
|
||||
"options": "Scheduled Job Type",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2019-09-25 11:55:10.646458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Log",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/scheduled_job_log/scheduled_job_log.py
Normal file
10
frappe/core/doctype/scheduled_job_log/scheduled_job_log.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ScheduledJobLog(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestScheduledJobLog(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/core/doctype/scheduled_job_type/__init__.py
Normal file
0
frappe/core/doctype/scheduled_job_type/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Scheduled Job Type', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"actions": [
|
||||
{
|
||||
"action": "frappe.core.doctype.scheduled_job_type.scheduled_job_type.execute_event",
|
||||
"action_type": "Server Action",
|
||||
"label": "Execute"
|
||||
}
|
||||
],
|
||||
"creation": "2019-09-23 14:34:09.205368",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"stopped",
|
||||
"method",
|
||||
"frequency",
|
||||
"cron_format",
|
||||
"last_execution",
|
||||
"create_log"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "method",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Method",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "stopped",
|
||||
"fieldtype": "Check",
|
||||
"label": "Stopped"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.queue==='All'",
|
||||
"fieldname": "create_log",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Log"
|
||||
},
|
||||
{
|
||||
"fieldname": "last_execution",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Last Execution",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"depends_on": "eval:doc.queue==='Cron'",
|
||||
"fieldname": "cron_format",
|
||||
"fieldtype": "Data",
|
||||
"label": "Cron Format",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "frequency",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Frequency",
|
||||
"options": "All\nHourly\nHourly Long\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Scheduled Job Log",
|
||||
"link_fieldname": "scheduled_job_type"
|
||||
}
|
||||
],
|
||||
"modified": "2019-12-09 11:10:21.259929",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Scheduled Job Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
156
frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
Normal file
156
frappe/core/doctype/scheduled_job_type/scheduled_job_type.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, json
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import now_datetime, get_datetime
|
||||
from datetime import datetime
|
||||
from croniter import croniter
|
||||
from frappe.utils.background_jobs import enqueue, get_jobs
|
||||
|
||||
class ScheduledJobType(Document):
|
||||
def autoname(self):
|
||||
self.name = '.'.join(self.method.split('.')[-2:])
|
||||
|
||||
def validate(self):
|
||||
if self.frequency != 'All':
|
||||
# force logging for all events other than continuous ones (ALL)
|
||||
self.create_log = 1
|
||||
|
||||
def enqueue(self):
|
||||
# enqueue event if last execution is done
|
||||
if self.is_event_due():
|
||||
if frappe.flags.enqueued_jobs:
|
||||
frappe.flags.enqueued_jobs.append(self.method)
|
||||
|
||||
if frappe.flags.execute_job:
|
||||
self.execute()
|
||||
else:
|
||||
if not self.is_job_in_queue():
|
||||
enqueue('frappe.core.doctype.scheduled_job_type.scheduled_job_type.run_scheduled_job',
|
||||
queue = self.get_queue_name(), job_type=self.method)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_event_due(self, current_time = None):
|
||||
'''Return true if event is due based on time lapsed since last execution'''
|
||||
# if the next scheduled event is before NOW, then its due!
|
||||
return self.get_next_execution() <= (current_time or now_datetime())
|
||||
|
||||
def is_job_in_queue(self):
|
||||
queued_jobs = get_jobs(site=frappe.local.site, key='job_type')[frappe.local.site]
|
||||
return self.method in queued_jobs
|
||||
|
||||
def get_next_execution(self):
|
||||
CRON_MAP = {
|
||||
"Yearly": "0 0 1 1 *",
|
||||
"Annual": "0 0 1 1 *",
|
||||
"Monthly": "0 0 1 * *",
|
||||
"Monthly Long": "0 0 1 * *",
|
||||
"Weekly": "0 0 * * 0",
|
||||
"Weekly Long": "0 0 * * 0",
|
||||
"Daily": "0 0 * * *",
|
||||
"Daily Long": "0 0 * * *",
|
||||
"Hourly": "0 * * * *",
|
||||
"Hourly Long": "0 * * * *",
|
||||
"All": "0/" + str((frappe.get_conf().scheduler_interval or 240) // 60) + " * * * *",
|
||||
}
|
||||
|
||||
if not self.cron_format:
|
||||
self.cron_format = CRON_MAP[self.frequency]
|
||||
|
||||
return croniter(self.cron_format,
|
||||
get_datetime(self.last_execution or datetime(2000, 1, 1))).get_next(datetime)
|
||||
|
||||
def execute(self):
|
||||
self.scheduler_log = None
|
||||
try:
|
||||
self.log_status('Start')
|
||||
frappe.get_attr(self.method)()
|
||||
frappe.db.commit()
|
||||
self.log_status('Complete')
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
self.log_status('Failed')
|
||||
|
||||
def log_status(self, status):
|
||||
# log file
|
||||
frappe.logger(__name__).info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site))
|
||||
self.update_scheduler_log(status)
|
||||
|
||||
def update_scheduler_log(self, status):
|
||||
if not self.create_log:
|
||||
return
|
||||
if not self.scheduler_log:
|
||||
self.scheduler_log = frappe.get_doc(dict(doctype = 'Scheduled Job Log', scheduled_job_type=self.name)).insert(ignore_permissions=True)
|
||||
self.scheduler_log.db_set('status', status)
|
||||
if status == 'Failed':
|
||||
self.scheduler_log.db_set('details', frappe.get_traceback())
|
||||
if status == 'Start':
|
||||
self.db_set('last_execution', now_datetime(), update_modified=False)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_queue_name(self):
|
||||
return 'long' if ('Long' in self.frequency) else 'default'
|
||||
|
||||
def on_trash(self):
|
||||
frappe.db.sql('delete from `tabScheduled Job Log` where scheduled_job_type=%s', self.name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_event(doc):
|
||||
frappe.only_for('System Manager')
|
||||
doc = json.loads(doc)
|
||||
frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue()
|
||||
|
||||
def run_scheduled_job(job_type):
|
||||
'''This is a wrapper function that runs a hooks.scheduler_events method'''
|
||||
try:
|
||||
frappe.get_doc('Scheduled Job Type', dict(method=job_type)).execute()
|
||||
except Exception:
|
||||
print(frappe.get_traceback())
|
||||
|
||||
def sync_jobs():
|
||||
frappe.reload_doc('core', 'doctype', 'scheduled_job_type')
|
||||
all_events = []
|
||||
scheduler_events = frappe.get_hooks("scheduler_events")
|
||||
insert_events(all_events, scheduler_events)
|
||||
clear_events(all_events, scheduler_events)
|
||||
|
||||
def insert_events(all_events, scheduler_events):
|
||||
for event_type in scheduler_events:
|
||||
events = scheduler_events.get(event_type)
|
||||
if isinstance(events, dict):
|
||||
insert_cron_event(events, all_events)
|
||||
else:
|
||||
# hourly, daily etc
|
||||
insert_event_list(events, event_type, all_events)
|
||||
|
||||
def insert_cron_event(events, all_events):
|
||||
for cron_format in events:
|
||||
for event in events.get(cron_format):
|
||||
all_events.append(event)
|
||||
insert_single_event('Cron', event, cron_format)
|
||||
|
||||
def insert_event_list(events, event_type, all_events):
|
||||
for event in events:
|
||||
all_events.append(event)
|
||||
frequency = event_type.replace('_', ' ').title()
|
||||
insert_single_event(frequency, event)
|
||||
|
||||
def insert_single_event(frequency, event, cron_format = None):
|
||||
if not frappe.db.exists('Scheduled Job Type', dict(method=event)):
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Scheduled Job Type',
|
||||
method = event,
|
||||
cron_format = cron_format,
|
||||
frequency = frequency
|
||||
)).insert()
|
||||
|
||||
def clear_events(all_events, scheduler_events):
|
||||
for event in frappe.get_all('Scheduled Job Type', ('name', 'method')):
|
||||
if event.method not in all_events:
|
||||
frappe.delete_doc('Scheduled Job Type', event.name)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import get_datetime
|
||||
|
||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
|
||||
|
||||
class TestScheduledJobType(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not frappe.get_all('Scheduled Job Type', limit=1):
|
||||
frappe.db.rollback()
|
||||
frappe.db.sql('truncate `tabScheduled Job Type`')
|
||||
sync_jobs()
|
||||
frappe.db.commit()
|
||||
|
||||
def test_sync_jobs(self):
|
||||
all_job = frappe.get_doc('Scheduled Job Type',
|
||||
dict(method='frappe.email.queue.flush'))
|
||||
self.assertEqual(all_job.frequency, 'All')
|
||||
|
||||
daily_job = frappe.get_doc('Scheduled Job Type',
|
||||
dict(method='frappe.email.queue.clear_outbox'))
|
||||
self.assertEqual(daily_job.frequency, 'Daily')
|
||||
|
||||
# check if cron jobs are synced
|
||||
cron_job = frappe.get_doc('Scheduled Job Type',
|
||||
dict(method='frappe.oauth.delete_oauth2_data'))
|
||||
self.assertEqual(cron_job.frequency, 'Cron')
|
||||
self.assertEqual(cron_job.cron_format, '0/15 * * * *')
|
||||
|
||||
def test_daily_job(self):
|
||||
job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox'))
|
||||
job.db_set('last_execution', '2019-01-01 00:00:00')
|
||||
self.assertTrue(job.is_event_due(get_datetime('2019-01-02 00:00:06')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-01 00:00:06')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-01 23:59:59')))
|
||||
|
||||
def test_weekly_job(self):
|
||||
job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.social.doctype.energy_point_log.energy_point_log.send_weekly_summary'))
|
||||
job.db_set('last_execution', '2019-01-01 00:00:00')
|
||||
self.assertTrue(job.is_event_due(get_datetime('2019-01-06 00:00:01')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-02 00:00:06')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-05 23:59:59')))
|
||||
|
||||
def test_monthly_job(self):
|
||||
job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.doctype.auto_email_report.auto_email_report.send_monthly'))
|
||||
job.db_set('last_execution', '2019-01-01 00:00:00')
|
||||
self.assertTrue(job.is_event_due(get_datetime('2019-02-01 00:00:01')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-15 00:00:06')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-31 23:59:59')))
|
||||
|
||||
def test_cron_job(self):
|
||||
# runs every 15 mins
|
||||
job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.oauth.delete_oauth2_data'))
|
||||
job.db_set('last_execution', '2019-01-01 00:00:00')
|
||||
self.assertTrue(job.is_event_due(get_datetime('2019-01-01 00:15:01')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-01 00:05:06')))
|
||||
self.assertFalse(job.is_event_due(get_datetime('2019-01-01 00:14:59')))
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2014-04-17 16:53:52.640856",
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
"backup_limit",
|
||||
"background_workers",
|
||||
"enable_scheduler",
|
||||
"scheduler_last_event",
|
||||
"dormant_days",
|
||||
"permissions",
|
||||
"apply_strict_user_permissions",
|
||||
"column_break_21",
|
||||
|
|
@ -168,13 +169,6 @@
|
|||
"hidden": 1,
|
||||
"label": "Enable Scheduled Jobs"
|
||||
},
|
||||
{
|
||||
"fieldname": "scheduler_last_event",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Scheduler Last Event",
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "permissions",
|
||||
|
|
@ -397,11 +391,18 @@
|
|||
"fieldname": "allow_guests_to_upload_files",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Guests to Upload Files"
|
||||
},
|
||||
{
|
||||
"default": "4",
|
||||
"description": "Will run scheduled jobs only once a day for inactive sites. Default 4 days if set to 0.",
|
||||
"fieldname": "dormant_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Run Jobs only Daily if Inactive For (Days)"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"modified": "2019-08-16 08:26:45.936626",
|
||||
"modified": "2019-09-24 10:04:28.807388",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Test Runner", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('Test Runner', [
|
||||
// insert a new Test Runner
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright (c) 2017, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Test Runner', {
|
||||
refresh: (frm) => {
|
||||
frm.disable_save();
|
||||
frm.page.set_primary_action(__("Run Tests"), () => {
|
||||
return new Promise(resolve => {
|
||||
let wrapper = $(frm.fields_dict.output.wrapper).empty();
|
||||
$("<p>Loading...</p>").appendTo(wrapper);
|
||||
|
||||
// all tests
|
||||
frappe.call({
|
||||
method: 'frappe.core.doctype.test_runner.test_runner.get_test_js',
|
||||
args: { test_path: frm.doc.module_path }
|
||||
}).always((data) => {
|
||||
$("<div id='qunit'></div>").appendTo(wrapper.empty());
|
||||
frm.events.run_tests(frm, data.message);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
run_tests: function(frm, files) {
|
||||
frappe.flags.in_test = true;
|
||||
let require_list = [
|
||||
"assets/frappe/js/lib/jquery/qunit.js",
|
||||
"assets/frappe/js/lib/jquery/qunit.css"
|
||||
].concat();
|
||||
|
||||
frappe.require(require_list, () => {
|
||||
files.forEach((f) => {
|
||||
frappe.dom.eval(f.script);
|
||||
});
|
||||
|
||||
QUnit.config.notrycatch = true;
|
||||
|
||||
window.onerror = function(msg, url, lineNo, columnNo, error) {
|
||||
console.log(error.stack); // eslint-disable-line
|
||||
$('<div id="frappe-qunit-done"></div>').appendTo($('body'));
|
||||
};
|
||||
|
||||
QUnit.testDone(function(details) {
|
||||
// var result = {
|
||||
// "Module name": details.module,
|
||||
// "Test name": details.name,
|
||||
// "Assertions": {
|
||||
// "Total": details.total,
|
||||
// "Passed": details.passed,
|
||||
// "Failed": details.failed
|
||||
// },
|
||||
// "Skipped": details.skipped,
|
||||
// "Todo": details.todo,
|
||||
// "Runtime": details.runtime
|
||||
// };
|
||||
|
||||
// eslint-disable-next-line
|
||||
// console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
details.assertions.map(a => {
|
||||
// eslint-disable-next-line
|
||||
console.log(`${a.result ? '✔' : '✗'} ${a.message}`);
|
||||
});
|
||||
|
||||
});
|
||||
QUnit.load();
|
||||
|
||||
QUnit.done(({ total, failed, passed, runtime }) => {
|
||||
// flag for selenium that test is done
|
||||
|
||||
console.log( `Total: ${total}, Failed: ${failed}, Passed: ${passed}, Runtime: ${runtime}` ); // eslint-disable-line
|
||||
|
||||
if(failed) {
|
||||
console.log('Tests Failed'); // eslint-disable-line
|
||||
} else {
|
||||
console.log('Tests Passed'); // eslint-disable-line
|
||||
}
|
||||
frappe.set_route('Form', 'Test Runner', 'Test Runner');
|
||||
|
||||
$('<div id="frappe-qunit-done"></div>').appendTo($('body'));
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-06-26 10:57:19.976624",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "module_path",
|
||||
"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": "Module Path",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "app",
|
||||
"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": "App",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "output",
|
||||
"fieldtype": "HTML",
|
||||
"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": "Output",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-19 03:22:33.221169",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Test Runner",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Administrator",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, os
|
||||
from frappe.model.document import Document
|
||||
|
||||
class TestRunner(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_test_js(test_path=None):
|
||||
'''Get test + data for app, example: app/tests/ui/test_name.js'''
|
||||
if not test_path:
|
||||
test_path = frappe.db.get_single_value('Test Runner', 'module_path')
|
||||
test_js = []
|
||||
|
||||
# split
|
||||
app, test_path = test_path.split(os.path.sep, 1)
|
||||
|
||||
# now full path
|
||||
test_path = frappe.get_app_path(app, test_path)
|
||||
|
||||
def add_file(path):
|
||||
with open(path, 'r') as fileobj:
|
||||
test_js.append(dict(
|
||||
script = fileobj.read()
|
||||
))
|
||||
|
||||
# add test_lib.js
|
||||
add_file(frappe.get_app_path('frappe', 'tests', 'ui', 'data', 'test_lib.js'))
|
||||
add_file(test_path)
|
||||
|
||||
return test_js
|
||||
|
||||
|
|
@ -97,7 +97,9 @@ class User(Document):
|
|||
self.share_with_self()
|
||||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
self.send_password_notification(self.__new_password)
|
||||
if self.__new_password:
|
||||
self.send_password_notification(self.__new_password)
|
||||
self.reset_password_key = ''
|
||||
create_contact(self, ignore_mandatory=True)
|
||||
if self.name not in ('Administrator', 'Guest') and not self.user_image:
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name)
|
||||
|
|
@ -1071,4 +1073,4 @@ def generate_keys(user):
|
|||
user_details.save()
|
||||
|
||||
return {"api_secret": api_secret}
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
<tbody>
|
||||
{% for j in jobs %}
|
||||
<tr>
|
||||
<td><span class="indicator {{ j.color }}" title="{{ j.status }}">{{ j.queue.split(".").slice(-1)[0] }}</span></td>
|
||||
<td><span class="indicator {{ j.color }}" title="{{ j.get_status() }}">{{ j.queue.split(".").slice(-1)[0] }}</span></td>
|
||||
<td style="overflow: auto;">
|
||||
<div>
|
||||
{{ frappe.utils.encode_tags(j.job_name) }}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ def get_info(show_failed=False):
|
|||
jobs.append({
|
||||
'job_name': j.kwargs.get('kwargs', {}).get('playbook_method') \
|
||||
or str(j.kwargs.get('job_name')),
|
||||
'status': j.status, 'queue': name,
|
||||
'status': j.get_status(), 'queue': name,
|
||||
'creation': format_datetime(convert_utc_to_user_timezone(j.created_at)),
|
||||
'color': colors[j.status]
|
||||
'color': colors[j.get_status()]
|
||||
})
|
||||
if j.exc_info:
|
||||
jobs[-1]['exc_info'] = j.exc_info
|
||||
|
|
|
|||
|
|
@ -36,13 +36,15 @@ def generate_and_cache_results(chart, chart_name, function, cache_key):
|
|||
|
||||
def get_from_date_from_timespan(to_date, timespan):
|
||||
days = months = years = 0
|
||||
if "Last Week" == timespan:
|
||||
if timespan == "Last Week":
|
||||
days = -7
|
||||
if "Last Month" == timespan:
|
||||
if timespan == "Last Month":
|
||||
months = -1
|
||||
elif "Last Quarter" == timespan:
|
||||
elif timespan == "Last Quarter":
|
||||
months = -3
|
||||
elif "Last Year" == timespan:
|
||||
elif timespan == "Last Year":
|
||||
years = -1
|
||||
elif timespan == "All Time":
|
||||
years = -50
|
||||
return add_to_date(to_date, years=years, months=months, days=days,
|
||||
as_datetime=True)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import frappe.model.meta
|
|||
|
||||
from frappe import _
|
||||
from time import time
|
||||
from frappe.utils import now, getdate, cast_fieldtype
|
||||
from frappe.utils import now, getdate, cast_fieldtype, get_datetime
|
||||
from frappe.utils.background_jobs import execute_job, get_queue
|
||||
from frappe.model.utils.link_count import flush_local_link_count
|
||||
from frappe.utils import cint
|
||||
|
|
@ -941,6 +941,16 @@ class Database(object):
|
|||
else:
|
||||
frappe.throw(_('No conditions provided'))
|
||||
|
||||
def get_last_created(self, doctype):
|
||||
last_record = self.get_all(doctype, ('creation'), limit=1, order_by='creation desc')
|
||||
if last_record:
|
||||
return get_datetime(last_record[0].creation)
|
||||
else:
|
||||
return None
|
||||
|
||||
def clear_table(self, doctype):
|
||||
self.sql('truncate `tab{}`'.format(doctype))
|
||||
|
||||
def log_touched_tables(self, query, values=None):
|
||||
if values:
|
||||
query = frappe.safe_decode(self._cursor.mogrify(query, values))
|
||||
|
|
|
|||
|
|
@ -80,12 +80,14 @@ class DbManager:
|
|||
if pipe:
|
||||
print('Creating Database...')
|
||||
|
||||
command = '{pipe} mysql -u {user} -p{password} -h{host} {target} {source}'.format(
|
||||
command = '{pipe} mysql -u {user} -p{password} -h{host} ' + ('-P{port}' if frappe.db.port else '') + ' {target} {source}'
|
||||
command = command.format(
|
||||
pipe=pipe,
|
||||
user=esc(user),
|
||||
password=esc(password),
|
||||
host=esc(frappe.db.host),
|
||||
target=esc(target),
|
||||
source=source
|
||||
source=source,
|
||||
port=frappe.db.port
|
||||
)
|
||||
os.system(command)
|
||||
|
|
|
|||
|
|
@ -105,6 +105,53 @@ CREATE TABLE `tabDocPerm` (
|
|||
KEY `parent` (`parent`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
--
|
||||
-- Table structure for table `tabDocType Action`
|
||||
--
|
||||
|
||||
CREATE TABLE `tabDocType Action` (
|
||||
`name` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`creation` datetime(6) DEFAULT NULL,
|
||||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`owner` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`parent` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parentfield` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parenttype` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`label` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action_type` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `modified` (`modified`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;
|
||||
|
||||
--
|
||||
-- Table structure for table `tabDocType Action`
|
||||
--
|
||||
|
||||
CREATE TABLE `tabDocType Link` (
|
||||
`name` varchar(140) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`creation` datetime(6) DEFAULT NULL,
|
||||
`modified` datetime(6) DEFAULT NULL,
|
||||
`modified_by` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`owner` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`docstatus` int(1) NOT NULL DEFAULT 0,
|
||||
`parent` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parentfield` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`parenttype` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`idx` int(8) NOT NULL DEFAULT 0,
|
||||
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`link_doctype` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`link_fieldname` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `modified` (`modified`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED;
|
||||
|
||||
--
|
||||
-- Table structure for table `tabDocType`
|
||||
--
|
||||
|
|
|
|||
|
|
@ -106,6 +106,57 @@ CREATE TABLE "tabDocPerm" (
|
|||
|
||||
create index on "tabDocPerm" ("parent");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDocType Action"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabDocType Action";
|
||||
CREATE TABLE "tabDocType Action" (
|
||||
"name" varchar(255) NOT NULL,
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"label" varchar(140) NOT NULL,
|
||||
"group" varchar(140) DEFAULT NULL,
|
||||
"action_type" varchar(140) NOT NULL,
|
||||
"action" varchar(140) NOT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
) ;
|
||||
|
||||
create index on "tabDocType Action" ("parent");
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDocType Link"
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS "tabDocType Link";
|
||||
CREATE TABLE "tabDocType Link" (
|
||||
"name" varchar(255) NOT NULL,
|
||||
"creation" timestamp(6) DEFAULT NULL,
|
||||
"modified" timestamp(6) DEFAULT NULL,
|
||||
"modified_by" varchar(255) DEFAULT NULL,
|
||||
"owner" varchar(255) DEFAULT NULL,
|
||||
"docstatus" smallint NOT NULL DEFAULT 0,
|
||||
"parent" varchar(255) DEFAULT NULL,
|
||||
"parentfield" varchar(255) DEFAULT NULL,
|
||||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"label" varchar(140) DEFAULT NULL,
|
||||
"group" varchar(140) DEFAULT NULL,
|
||||
"link_doctype" varchar(140) NOT NULL,
|
||||
"link_fieldname" varchar(140) NOT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
) ;
|
||||
|
||||
create index on "tabDocType Link" ("parent");
|
||||
|
||||
|
||||
--
|
||||
-- Table structure for table "tabDocType"
|
||||
--
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@
|
|||
|
||||
frappe.ui.form.on('Bulk Update', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("document_type", function() {
|
||||
return {
|
||||
filters: [
|
||||
['DocType', 'issingle', '=', 0],
|
||||
['DocType', 'name', 'not in', frappe.model.core_doctypes_list]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
frm.page.set_primary_action(__('Update'), function() {
|
||||
if (!frm.doc.update_value) {
|
||||
frappe.throw(__('Field "value" is mandatory. Please specify value to be updated'));
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
timespan: function(frm) {
|
||||
const time_interval_options = {
|
||||
"Select Date Range": ["Quarterly", "Monthly", "Weekly", "Daily"],
|
||||
"All Time": ["Yearly", "Monthly"],
|
||||
"Last Year": ["Quarterly", "Monthly", "Weekly", "Daily"],
|
||||
"Last Quarter": ["Monthly", "Weekly", "Daily"],
|
||||
"Last Month": ["Weekly", "Daily"],
|
||||
|
|
|
|||
|
|
@ -82,14 +82,14 @@
|
|||
"fieldname": "timespan",
|
||||
"fieldtype": "Select",
|
||||
"label": "Timespan",
|
||||
"options": "Last Year\nLast Quarter\nLast Month\nLast Week\nSelect Date Range"
|
||||
"options": "All Time\nLast Year\nLast Quarter\nLast Month\nLast Week\nSelect Date Range"
|
||||
},
|
||||
{
|
||||
"depends_on": "timeseries",
|
||||
"fieldname": "time_interval",
|
||||
"fieldtype": "Select",
|
||||
"label": "Time Interval",
|
||||
"options": "Quarterly\nMonthly\nWeekly\nDaily"
|
||||
"options": "Yearly\nQuarterly\nMonthly\nWeekly\nDaily"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"label": "To Date"
|
||||
}
|
||||
],
|
||||
"modified": "2019-11-04 12:32:14.525409",
|
||||
"modified": "2019-11-18 16:20:11.529496",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart",
|
||||
|
|
|
|||
|
|
@ -74,8 +74,7 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date):
|
|||
result = convert_to_dates(data, timegrain)
|
||||
|
||||
# add missing data points for periods where there was no result
|
||||
result = add_missing_values(result, timegrain, from_date, to_date)
|
||||
|
||||
result = add_missing_values(result, timegrain, timespan, from_date, to_date)
|
||||
chart_config = {
|
||||
"labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
|
||||
"datasets": [{
|
||||
|
|
@ -133,7 +132,9 @@ def get_aggregate_function(chart_type):
|
|||
"Average": "AVG",
|
||||
}[chart_type]
|
||||
|
||||
|
||||
def convert_to_dates(data, timegrain):
|
||||
""" Converts individual dates within data to the end of period """
|
||||
result = []
|
||||
for d in data:
|
||||
if timegrain == 'Daily':
|
||||
|
|
@ -141,10 +142,11 @@ def convert_to_dates(data, timegrain):
|
|||
elif timegrain == 'Weekly':
|
||||
result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), weeks = d[1] + 1), days = -1), d[2]])
|
||||
elif timegrain == 'Monthly':
|
||||
result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months = d[1]), days = -1), d[2]])
|
||||
result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=d[1]), days = -1), d[2]])
|
||||
elif timegrain == 'Quarterly':
|
||||
result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months = d[1] * 3), days = -1), d[2]])
|
||||
|
||||
result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=d[1] * 3), days = -1), d[2]])
|
||||
elif timegrain == 'Yearly':
|
||||
result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=12), days = -1), d[2]])
|
||||
result[-1][0] = getdate(result[-1][0])
|
||||
|
||||
return result
|
||||
|
|
@ -164,17 +166,17 @@ def get_unit_function(datefield, timegrain):
|
|||
|
||||
return unit_function
|
||||
|
||||
def add_missing_values(data, timegrain, from_date, to_date):
|
||||
def add_missing_values(data, timegrain, timespan, from_date, to_date):
|
||||
# add missing intervals
|
||||
result = []
|
||||
|
||||
first_expected_date = get_period_ending(from_date, timegrain)
|
||||
|
||||
# fill out data before the first data point
|
||||
first_data_point_date = data[0][0] if data else getdate(add_to_date(to_date, days=1))
|
||||
while first_data_point_date > first_expected_date:
|
||||
result.append([first_expected_date, 0.0])
|
||||
first_expected_date = get_next_expected_date(first_expected_date, timegrain)
|
||||
if timespan != 'All Time':
|
||||
first_expected_date = get_period_ending(from_date, timegrain)
|
||||
# fill out data before the first data point
|
||||
first_data_point_date = data[0][0] if data else getdate(add_to_date(to_date, days=1))
|
||||
while first_data_point_date > first_expected_date:
|
||||
result.append([first_expected_date, 0.0])
|
||||
first_expected_date = get_next_expected_date(first_expected_date, timegrain)
|
||||
|
||||
# fill data points and missing points
|
||||
for i, d in enumerate(data):
|
||||
|
|
@ -212,14 +214,16 @@ def get_next_expected_date(date, timegrain):
|
|||
|
||||
def get_period_ending(date, timegrain):
|
||||
date = getdate(date)
|
||||
if timegrain=='Daily':
|
||||
if timegrain == 'Daily':
|
||||
pass
|
||||
elif timegrain=='Weekly':
|
||||
elif timegrain == 'Weekly':
|
||||
date = get_week_ending(date)
|
||||
elif timegrain=='Monthly':
|
||||
elif timegrain == 'Monthly':
|
||||
date = get_month_ending(date)
|
||||
elif timegrain=='Quarterly':
|
||||
elif timegrain == 'Quarterly':
|
||||
date = get_quarter_ending(date)
|
||||
elif timegrain == 'Yearly':
|
||||
date = get_year_ending(date)
|
||||
|
||||
return getdate(date)
|
||||
|
||||
|
|
@ -231,7 +235,7 @@ def get_week_ending(date):
|
|||
# first day of next week
|
||||
date = add_to_date('{}-01-01'.format(date.year), weeks = week_of_the_year + 1)
|
||||
# last day of this week
|
||||
return add_to_date(date, days = -1)
|
||||
return add_to_date(date, days=-1)
|
||||
|
||||
def get_month_ending(date):
|
||||
month_of_the_year = int(date.strftime('%m'))
|
||||
|
|
@ -239,7 +243,7 @@ def get_month_ending(date):
|
|||
|
||||
date = add_to_date('{}-01-01'.format(date.year), months = month_of_the_year)
|
||||
# last day of this month
|
||||
return add_to_date(date, days = -1)
|
||||
return add_to_date(date, days=-1)
|
||||
|
||||
def get_quarter_ending(date):
|
||||
date = getdate(date)
|
||||
|
|
@ -255,8 +259,17 @@ def get_quarter_ending(date):
|
|||
|
||||
return date
|
||||
|
||||
def get_year_ending(date):
|
||||
''' returns year ending of the given date '''
|
||||
|
||||
# first day of next year (note year starts from 1)
|
||||
date = add_to_date('{}-01-01'.format(date.year), months = 12)
|
||||
# last day of this month
|
||||
return add_to_date(date, days=-1)
|
||||
|
||||
|
||||
class DashboardChart(Document):
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().delete_key('chart-data:{}'.format(self.name))
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class TestDashboardChart(unittest.TestCase):
|
|||
self.assertEqual(get_period_ending('2019-10-01', 'Quarterly'),
|
||||
getdate('2019-12-31'))
|
||||
|
||||
self.assertEqual(get_period_ending('2019-10-01', 'Yearly'),
|
||||
getdate('2019-12-31'))
|
||||
|
||||
def test_dashboard_chart(self):
|
||||
if frappe.db.exists('Dashboard Chart', 'Test Dashboard Chart'):
|
||||
frappe.delete_doc('Dashboard Chart', 'Test Dashboard Chart')
|
||||
|
|
|
|||
|
|
@ -29,12 +29,16 @@ class GlobalSearchSettings(Document):
|
|||
repeated_dts = (", ".join([frappe.bold(dt) for dt in repeated_dts]))
|
||||
frappe.throw(_("Document Type {0} has been repeated.").format(repeated_dts))
|
||||
|
||||
def get_doctypes_for_global_search():
|
||||
doctypes = frappe.get_list("Global Search DocType", fields=["document_type"], order_by="idx ASC")
|
||||
if not doctypes:
|
||||
return []
|
||||
# reset cache
|
||||
frappe.cache().hdel('global_search', 'search_priorities')
|
||||
|
||||
def get_doctypes_for_global_search():
|
||||
def get_from_db():
|
||||
doctypes = frappe.get_list("Global Search DocType", fields=["document_type"], order_by="idx ASC")
|
||||
return [d.document_type for d in doctypes] or []
|
||||
|
||||
return frappe.cache().hget("global_search", "search_priorities", get_from_db)
|
||||
|
||||
return [d.document_type for d in doctypes]
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset_global_search_settings_doctypes():
|
||||
|
|
@ -57,7 +61,7 @@ def update_global_search_doctypes():
|
|||
if search_doctypes.get(domain):
|
||||
global_search_doctypes.extend(search_doctypes.get(domain))
|
||||
|
||||
doctype_list = set([dt.name for dt in frappe.get_list("DocType")])
|
||||
doctype_list = set([dt.name for dt in frappe.get_all("DocType")])
|
||||
allowed_in_global_search = []
|
||||
|
||||
for dt in global_search_doctypes:
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ def enqueue_create_notification(users, doc):
|
|||
This breaks new site creation if Redis server is not running.
|
||||
We do not need any notifications in fresh installation
|
||||
'''
|
||||
|
||||
if frappe.flags.in_install:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Notification Settings', {
|
||||
onload: () => {
|
||||
frappe.breadcrumbs.add({
|
||||
label: __('Settings'),
|
||||
route: '#modules/Settings',
|
||||
type: 'Custom'
|
||||
});
|
||||
}
|
||||
});
|
||||
0
frappe/desk/doctype/onboarding_slide/__init__.py
Normal file
0
frappe/desk/doctype/onboarding_slide/__init__.py
Normal file
45
frappe/desk/doctype/onboarding_slide/onboarding_slide.js
Normal file
45
frappe/desk/doctype/onboarding_slide/onboarding_slide.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) 2019, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Onboarding Slide', {
|
||||
refresh: function(frm) {
|
||||
frm.toggle_reqd('ref_doctype', (frm.doc.slide_type=='Create' || frm.doc.slide_type=='Settings'));
|
||||
frm.toggle_reqd('slide_module', (frm.doc.slide_type=='Information' || frm.doc.slide_type=='Continue'));
|
||||
},
|
||||
|
||||
ref_doctype: function(frm) {
|
||||
frm.set_query('ref_doctype', function() {
|
||||
if (frm.doc.slide_type === 'Create') {
|
||||
return {
|
||||
filters: {
|
||||
'issingle': 0,
|
||||
'istable': 0
|
||||
}
|
||||
};
|
||||
} else if (frm.doc.slide_type === 'Settings') {
|
||||
return {
|
||||
filters: {
|
||||
'issingle': 1,
|
||||
'istable': 0
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
//fetch mandatory fields automatically
|
||||
if (frm.doc.ref_doctype) {
|
||||
frappe.model.clear_table(frm.doc, 'slide_fields');
|
||||
let fields = frappe.meta.get_docfields(frm.doc.ref_doctype, null, {
|
||||
reqd: 1
|
||||
});
|
||||
$.each(fields, function(_i, data) {
|
||||
let row = frappe.model.add_child(frm.doc, 'Onboarding Slide', 'slide_fields');
|
||||
row.label = data.label;
|
||||
row.fieldtype = data.fieldtype;
|
||||
row.fieldname = data.fieldname;
|
||||
row.options = data.options;
|
||||
});
|
||||
refresh_field('slide_fields');
|
||||
}
|
||||
}
|
||||
});
|
||||
184
frappe/desk/doctype/onboarding_slide/onboarding_slide.json
Normal file
184
frappe/desk/doctype/onboarding_slide/onboarding_slide.json
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"autoname": "field:slide_title",
|
||||
"creation": "2019-11-13 14:39:56.834658",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"slide_title",
|
||||
"app",
|
||||
"slide_order",
|
||||
"column_break_4",
|
||||
"image_src",
|
||||
"slide_module",
|
||||
"description_section_break",
|
||||
"slide_desc",
|
||||
"action_section_break",
|
||||
"slide_type",
|
||||
"column_break_6",
|
||||
"max_count",
|
||||
"add_more_button",
|
||||
"section_break_18",
|
||||
"ref_doctype",
|
||||
"slide_fields",
|
||||
"section_break_10",
|
||||
"domains",
|
||||
"column_break_12",
|
||||
"help_links",
|
||||
"is_completed"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "slide_title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Slide Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "slide_desc",
|
||||
"fieldtype": "HTML Editor",
|
||||
"label": "Slide Description"
|
||||
},
|
||||
{
|
||||
"default": "3",
|
||||
"depends_on": "add_more_button",
|
||||
"description": "The amount of times you want to repeat the set of fields (eg: if you want 3 customers in the slide, set this field to 3. Only the first set of fields is shown as mandatory in the slide)",
|
||||
"fieldname": "max_count",
|
||||
"fieldtype": "Int",
|
||||
"label": "Max Count"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
|
||||
"fieldname": "add_more_button",
|
||||
"fieldtype": "Check",
|
||||
"label": "Add More Button"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
|
||||
"fieldname": "slide_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "Slide Fields",
|
||||
"options": "Onboarding Slide Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "Specify in what all domains should the slides show up. If nothing is specified the slide is shown in all domains by default.",
|
||||
"fieldname": "domains",
|
||||
"fieldtype": "Table",
|
||||
"label": "Domains",
|
||||
"options": "Has Domain"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Add a help video link just in case user has no idea about what to fill in the slide.",
|
||||
"fieldname": "help_links",
|
||||
"fieldtype": "Table",
|
||||
"label": "Help Links",
|
||||
"options": "Onboarding Slide Help Link"
|
||||
},
|
||||
{
|
||||
"fieldname": "action_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Action Settings"
|
||||
},
|
||||
{
|
||||
"description": "If Slide Type is Create or Settings there should be a 'create_onboarding_docs' method in the {ref_doctype}.py file bound to be executed after the slide is completed.",
|
||||
"fieldname": "slide_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Slide Type",
|
||||
"options": "Information\nCreate\nSettings\nContinue",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "app",
|
||||
"fieldtype": "Select",
|
||||
"label": "App",
|
||||
"options": "Frappe\nERPNext",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "image_src",
|
||||
"fieldtype": "Data",
|
||||
"label": "Slide Image Source"
|
||||
},
|
||||
{
|
||||
"fieldname": "description_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
|
||||
"fieldname": "ref_doctype",
|
||||
"fieldtype": "Link",
|
||||
"label": "Reference Document Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Determines the order of the slide in the wizard. If the slide is not to be displayed, priority should be set to 0.",
|
||||
"fieldname": "slide_order",
|
||||
"fieldtype": "Int",
|
||||
"label": "Slide Order"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.slide_type=='Information' || doc.slide_type=='Continue'",
|
||||
"fieldname": "slide_module",
|
||||
"fieldtype": "Link",
|
||||
"label": "Module",
|
||||
"options": "Module Def"
|
||||
},
|
||||
{
|
||||
"collapsible_depends_on": "eval:doc.slide_type=='Create' || doc.slide_type=='Settings'",
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Fields"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_completed",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Completed",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"modified": "2019-12-04 10:50:43.528901",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Onboarding Slide",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
134
frappe/desk/doctype/onboarding_slide/onboarding_slide.py
Normal file
134
frappe/desk/doctype/onboarding_slide/onboarding_slide.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.modules.export_file import export_to_files
|
||||
|
||||
class OnboardingSlide(Document):
|
||||
def validate(self):
|
||||
if self.slide_type == 'Continue' and frappe.db.exists('Onboarding Slide', {'slide_type': 'Continue', 'name': ('!=', self.name)}):
|
||||
frappe.throw(_('An Onboarding Slide of Slide Type Continue already exists.'))
|
||||
|
||||
if self.slide_order:
|
||||
same_order_slide = frappe.db.exists('Onboarding Slide', {'slide_order': self.slide_order, 'name': ('!=', self.name)})
|
||||
if same_order_slide:
|
||||
frappe.throw(_('An Onboarding Slide <b>{0}</b> with the same slide order already exists').format(same_order_slide))
|
||||
|
||||
def on_update(self):
|
||||
if self.ref_doctype:
|
||||
module = frappe.db.get_value('DocType', self.ref_doctype, 'module')
|
||||
else:
|
||||
module = self.slide_module
|
||||
export_to_files(record_list=[['Onboarding Slide', self.name]], record_module=module)
|
||||
|
||||
def get_onboarding_slides_as_list():
|
||||
slides = []
|
||||
slide_docs = frappe.db.get_all('Onboarding Slide',
|
||||
filters={'is_completed': 0},
|
||||
or_filters={'slide_order': ('!=', 0), 'slide_type': 'Continue'},
|
||||
order_by='slide_order')
|
||||
|
||||
# to check if continue slide is required
|
||||
first_slide = get_first_slide()
|
||||
|
||||
for entry in slide_docs:
|
||||
# using get_doc because child table fields are not fetched in get_all
|
||||
slide_doc = frappe.get_doc('Onboarding Slide', entry.name)
|
||||
if frappe.scrub(slide_doc.app) in frappe.get_installed_apps():
|
||||
slide = frappe._dict(
|
||||
slide_type=slide_doc.slide_type,
|
||||
title=slide_doc.slide_title,
|
||||
help=slide_doc.slide_desc,
|
||||
fields=slide_doc.slide_fields,
|
||||
help_links=get_help_links(slide_doc),
|
||||
add_more=slide_doc.add_more_button,
|
||||
max_count=slide_doc.max_count,
|
||||
image_src=get_slide_image(slide_doc),
|
||||
ref_doctype=slide_doc.ref_doctype,
|
||||
app=slide_doc.app
|
||||
)
|
||||
if slide.slide_type == 'Continue':
|
||||
if is_continue_slide_required(first_slide):
|
||||
slides.insert(0, slide)
|
||||
else:
|
||||
slides.append(slide)
|
||||
|
||||
return slides
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_onboarding_slides():
|
||||
slides = []
|
||||
slide_list = get_onboarding_slides_as_list()
|
||||
|
||||
active_domains = frappe.get_active_domains()
|
||||
for slide in slide_list:
|
||||
if not slide.domains or any(domain in active_domains for domain in slide.domains):
|
||||
slides.append(slide)
|
||||
return slides
|
||||
|
||||
def get_help_links(slide_doc):
|
||||
links=[]
|
||||
for link in slide_doc.help_links:
|
||||
links.append({
|
||||
'label': link.label,
|
||||
'video_id': link.video_id
|
||||
})
|
||||
return links
|
||||
|
||||
def get_slide_image(slide_doc):
|
||||
if slide_doc.image_src:
|
||||
return slide_doc.image_src
|
||||
return None
|
||||
|
||||
def is_continue_slide_required(first_slide):
|
||||
# check if first slide itself is not completed
|
||||
if not first_slide.is_completed:
|
||||
return False
|
||||
|
||||
# check if there is any active slide which is not completed
|
||||
return frappe.db.exists('Onboarding Slide', {
|
||||
'is_completed': 0,
|
||||
'slide_order': ('!=', 0),
|
||||
'slide_type': ('!=', 'Continue')
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_onboarding_docs(values, doctype=None, app=None, slide_type=None):
|
||||
data = json.loads(values)
|
||||
doc = frappe.new_doc(doctype)
|
||||
if hasattr(doc, 'create_onboarding_docs'):
|
||||
doc.create_onboarding_docs(data)
|
||||
else:
|
||||
create_generic_onboarding_doc(data, doctype, slide_type)
|
||||
|
||||
def create_generic_onboarding_doc(data, doctype, slide_type):
|
||||
if slide_type == 'Settings':
|
||||
doc = frappe.get_single(doctype)
|
||||
for entry in data:
|
||||
doc.set(entry, data.get(entry))
|
||||
doc.save()
|
||||
|
||||
elif slide_type == 'Create':
|
||||
doc = frappe.new_doc(doctype)
|
||||
for entry in data:
|
||||
doc.set(entry, data.get(entry))
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.flags.ignore_links = True
|
||||
doc.insert()
|
||||
|
||||
@frappe.whitelist()
|
||||
def mark_slide_as_completed(slide_title):
|
||||
frappe.db.set_value('Onboarding Slide', slide_title, 'is_completed', 1)
|
||||
|
||||
def get_first_slide():
|
||||
slides = frappe.db.get_all('Onboarding Slide',
|
||||
filters={'slide_order': ('!=', 0), 'slide_type': ('!=', 'Continue')},
|
||||
order_by='slide_order',
|
||||
fields=['name', 'is_completed']
|
||||
)
|
||||
return slides[0]
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestOnboardingSlide(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/desk/doctype/onboarding_slide_field/__init__.py
Normal file
0
frappe/desk/doctype/onboarding_slide_field/__init__.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"creation": "2019-11-13 13:35:08.617909",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"label",
|
||||
"fieldtype",
|
||||
"fieldname",
|
||||
"align",
|
||||
"placeholder",
|
||||
"reqd",
|
||||
"column_break_4",
|
||||
"options"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nRating\nSelect\nLink\nSmall Text\nText\nText Editor\nSection Break\nColumn Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname"
|
||||
},
|
||||
{
|
||||
"fieldname": "align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Align",
|
||||
"options": "\ncenter\nleft\nright"
|
||||
},
|
||||
{
|
||||
"fieldname": "placeholder",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Placeholder"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mandatory"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-12-02 16:43:51.930018",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Onboarding Slide Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class OnboardingSlideField(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"creation": "2019-11-19 12:22:42.805741",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"label",
|
||||
"video_id"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label"
|
||||
},
|
||||
{
|
||||
"fieldname": "video_id",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Video"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-11-19 13:39:57.716248",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Onboarding Slide Help Link",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class OnboardingSlideHelpLink(Document):
|
||||
pass
|
||||
|
|
@ -105,7 +105,7 @@ def get_next(doctype, value, prev, filters, sort_order, sort_field):
|
|||
res = frappe.get_list(doctype,
|
||||
fields = ["name"],
|
||||
filters = filters,
|
||||
order_by = sort_field + " " + sort_order,
|
||||
order_by = "`tab{0}`.{1}".format(doctype, sort_field) + " " + sort_order,
|
||||
limit_start=0, limit_page_length=1, as_list=True)
|
||||
|
||||
if not res:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def get_group_by_count(doctype, current_filters, field):
|
|||
else:
|
||||
return frappe.db.get_list(doctype,
|
||||
filters=current_filters,
|
||||
group_by=field,
|
||||
group_by='`tab{0}`.{1}'.format(doctype, field),
|
||||
fields=['count(*) as count', '`{}` as name'.format(field)],
|
||||
order_by='count desc',
|
||||
limit=50,
|
||||
|
|
|
|||
|
|
@ -234,8 +234,11 @@ def get_config(app, module):
|
|||
for item in section["items"]:
|
||||
if item["type"]=="report" and item["name"] in disabled_reports:
|
||||
continue
|
||||
# some module links might not have name
|
||||
if not item.get("name"):
|
||||
item["name"] = item.get("label")
|
||||
if not item.get("label"):
|
||||
item["label"] = _(item["name"])
|
||||
item["label"] = _(item.get("name"))
|
||||
items.append(item)
|
||||
section['items'] = items
|
||||
|
||||
|
|
@ -297,7 +300,7 @@ def get_onboard_items(app, module):
|
|||
|
||||
@frappe.whitelist()
|
||||
def get_links_for_module(app, module):
|
||||
return [l.get('label') for l in get_links(app, module)]
|
||||
return [{'value': l.get('name'), 'label': l.get('label')} for l in get_links(app, module)]
|
||||
|
||||
def get_links(app, module):
|
||||
try:
|
||||
|
|
@ -330,13 +333,13 @@ def get_desktop_settings():
|
|||
def apply_user_saved_links(module):
|
||||
module = frappe._dict(module)
|
||||
all_links = get_links(module.app, module.module_name)
|
||||
module_links_by_label = {}
|
||||
module_links_by_name = {}
|
||||
for link in all_links:
|
||||
module_links_by_label[link['label']] = link
|
||||
module_links_by_name[link['name']] = link
|
||||
|
||||
if module.module_name in user_saved_links_by_module:
|
||||
user_links = frappe.parse_json(user_saved_links_by_module[module.module_name])
|
||||
module.links = [module_links_by_label[l] for l in user_links if l in module_links_by_label]
|
||||
module.links = [module_links_by_name[l] for l in user_links if l in module_links_by_name]
|
||||
|
||||
return module
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,19 @@ import json
|
|||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_notifications():
|
||||
out = {
|
||||
"open_count_doctype": {},
|
||||
"targets": {},
|
||||
}
|
||||
if (frappe.flags.in_install or
|
||||
not frappe.db.get_single_value('System Settings', 'setup_complete')):
|
||||
return {
|
||||
"open_count_doctype": {},
|
||||
"targets": {},
|
||||
}
|
||||
return out
|
||||
|
||||
config = get_notification_config()
|
||||
|
||||
if not config:
|
||||
return out
|
||||
|
||||
groups = list(config.get("for_doctype")) + list(config.get("for_module"))
|
||||
cache = frappe.cache()
|
||||
|
||||
|
|
@ -31,10 +35,10 @@ def get_notifications():
|
|||
if count is not None:
|
||||
notification_count[name] = count
|
||||
|
||||
return {
|
||||
"open_count_doctype": get_notifications_for_doctypes(config, notification_count),
|
||||
"targets": get_notifications_for_targets(config, notification_percent),
|
||||
}
|
||||
out['open_count_doctype'] = get_notifications_for_doctypes(config, notification_count)
|
||||
out['targets'] = get_notifications_for_targets(config, notification_percent)
|
||||
|
||||
return out
|
||||
|
||||
def get_notifications_for_doctypes(config, notification_count):
|
||||
"""Notifications for DocTypes"""
|
||||
|
|
@ -118,6 +122,10 @@ def clear_notifications(user=None):
|
|||
return
|
||||
cache = frappe.cache()
|
||||
config = get_notification_config()
|
||||
|
||||
if not config:
|
||||
return
|
||||
|
||||
for_doctype = list(config.get('for_doctype')) if config.get('for_doctype') else []
|
||||
for_module = list(config.get('for_module')) if config.get('for_module') else []
|
||||
groups = for_doctype + for_module
|
||||
|
|
@ -139,6 +147,8 @@ def delete_notification_count_for(doctype):
|
|||
|
||||
def clear_doctype_notifications(doc, method=None, *args, **kwargs):
|
||||
config = get_notification_config()
|
||||
if not config:
|
||||
return
|
||||
if isinstance(doc, string_types):
|
||||
doctype = doc # assuming doctype name was passed directly
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@
|
|||
background: #f0f4f7;
|
||||
}
|
||||
|
||||
.from-date-field .clearfix{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.from-date-field {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.select-time:focus, .select-doctype:focus, .select-filter:focus, .select-sort:focus {
|
||||
background: #f0f4f7;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@ class Leaderboard {
|
|||
return field;
|
||||
});
|
||||
}
|
||||
this.timespans = ["Week", "Month", "Quarter", "Year", "All Time"];
|
||||
this.timespans = [
|
||||
"This Week", "This Month", "This Quarter", "This Year",
|
||||
"Last Week", "Last Month", "Last Quarter", "Last Year",
|
||||
"All Time", "Select From Date"
|
||||
];
|
||||
|
||||
// for saving current selected filters
|
||||
const _initial_doctype = frappe.get_route()[1] || this.doctypes[0];
|
||||
|
|
@ -103,7 +107,8 @@ class Leaderboard {
|
|||
this.timespans.map(d => {
|
||||
return {"label": __(d), value: d };
|
||||
})
|
||||
);
|
||||
);
|
||||
this.create_from_date_field();
|
||||
|
||||
this.type_select = this.page.add_select(__("Field"),
|
||||
this.options.selected_filter.map(d => {
|
||||
|
|
@ -113,7 +118,12 @@ class Leaderboard {
|
|||
|
||||
this.timespan_select.on("change", (e) => {
|
||||
this.options.selected_timespan = e.currentTarget.value;
|
||||
this.make_request();
|
||||
if (this.options.selected_timespan === 'Select From Date') {
|
||||
this.from_date_field.show();
|
||||
} else {
|
||||
this.from_date_field.hide();
|
||||
this.make_request();
|
||||
}
|
||||
});
|
||||
|
||||
this.type_select.on("change", (e) => {
|
||||
|
|
@ -122,6 +132,28 @@ class Leaderboard {
|
|||
});
|
||||
}
|
||||
|
||||
create_from_date_field() {
|
||||
let timespan_field = $(this.parent).find(`.frappe-control[data-original-title='Timespan']`);
|
||||
this.from_date_field = $(`<div class="from-date-field"></div>`).insertAfter(timespan_field).hide();
|
||||
|
||||
let date_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'Date',
|
||||
fieldname: 'selected_from_date',
|
||||
placeholder: frappe.datetime.month_start(),
|
||||
default: frappe.datetime.month_start(),
|
||||
input_class: 'input-sm',
|
||||
reqd: 1,
|
||||
change: () => {
|
||||
this.selected_from_date = date_field.get_value();
|
||||
if (this.selected_from_date) this.make_request();
|
||||
}
|
||||
},
|
||||
parent: $(this.parent).find('.from-date-field'),
|
||||
render_input: 1
|
||||
});
|
||||
}
|
||||
|
||||
render_selected_doctype() {
|
||||
|
||||
this.$sidebar_list.on("click", "li", (e)=> {
|
||||
|
|
@ -207,7 +239,6 @@ class Leaderboard {
|
|||
this.leaderboard_config[this.options.selected_doctype].method,
|
||||
{
|
||||
'from_date': this.get_from_date(),
|
||||
'timespan': this.options.selected_timespan,
|
||||
'company': this.options.selected_company,
|
||||
'field': this.options.selected_filter_item,
|
||||
'limit': this.leaderboard_limit,
|
||||
|
|
@ -360,17 +391,20 @@ class Leaderboard {
|
|||
get_from_date() {
|
||||
let timespan = this.options.selected_timespan.toLowerCase();
|
||||
let current_date = frappe.datetime.now_date();
|
||||
let date = '';
|
||||
if (timespan === "month") {
|
||||
date = frappe.datetime.add_months(current_date, -1);
|
||||
} else if (timespan === "quarter") {
|
||||
date = frappe.datetime.add_months(current_date, -3);
|
||||
} else if (timespan === "year") {
|
||||
date = frappe.datetime.add_months(current_date, -12);
|
||||
} else if (timespan === "week") {
|
||||
date = frappe.datetime.add_days(current_date, -7);
|
||||
let get_from_date = {
|
||||
"this week": frappe.datetime.week_start(),
|
||||
"this month": frappe.datetime.month_start(),
|
||||
"this quarter": frappe.datetime.quarter_start(),
|
||||
"this year": frappe.datetime.year_start(),
|
||||
"last week": frappe.datetime.add_days(current_date, -7),
|
||||
"last month": frappe.datetime.add_months(current_date, -1),
|
||||
"last quarter": frappe.datetime.add_months(current_date, -3),
|
||||
"last year": frappe.datetime.add_months(current_date, -12),
|
||||
"all time": "",
|
||||
"select from date": this.selected_from_date || frappe.datetime.month_start()
|
||||
}
|
||||
return date;
|
||||
|
||||
return get_from_date[timespan];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from frappe.desk.doctype.global_search_settings.global_search_settings import up
|
|||
def install():
|
||||
update_genders_and_salutations()
|
||||
update_global_search_doctypes()
|
||||
setup_email_linking()
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_genders_and_salutations():
|
||||
|
|
@ -20,13 +21,10 @@ def update_genders_and_salutations():
|
|||
for record in records:
|
||||
doc = frappe.new_doc(record.get("doctype"))
|
||||
doc.update(record)
|
||||
doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
|
||||
try:
|
||||
doc.insert(ignore_permissions=True)
|
||||
except frappe.DuplicateEntryError as e:
|
||||
# pass DuplicateEntryError and continue
|
||||
if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
|
||||
# make sure DuplicateEntryError is for the exact same doc and not a related doc
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
def setup_email_linking():
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Email Account",
|
||||
"email_id": "email_linking@example.com",
|
||||
}).insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
|
|
@ -9,8 +9,10 @@ from six.moves import range
|
|||
import frappe.permissions
|
||||
from frappe.model.db_query import DatabaseQuery
|
||||
from frappe import _
|
||||
from six import text_type, string_types, StringIO
|
||||
from six import string_types, StringIO
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
from frappe.utils import cstr
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
@ -170,11 +172,11 @@ def export_query():
|
|||
writer = csv.writer(f)
|
||||
for r in data:
|
||||
# encode only unicode type strings and not int, floats etc.
|
||||
writer.writerow([handle_html(frappe.as_unicode(v)).encode('utf-8') \
|
||||
writer.writerow([handle_html(frappe.as_unicode(v)) \
|
||||
if isinstance(v, string_types) else v for v in r])
|
||||
|
||||
f.seek(0)
|
||||
frappe.response['result'] = text_type(f.read(), 'utf-8')
|
||||
frappe.response['result'] = cstr(f.read())
|
||||
frappe.response['type'] = 'csv'
|
||||
frappe.response['doctype'] = title
|
||||
|
||||
|
|
@ -261,17 +263,24 @@ def delete_bulk(doctype, items):
|
|||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
def get_sidebar_stats(stats, doctype, filters=[]):
|
||||
_user_tags = []
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
filters = json.loads(data["filters"])
|
||||
|
||||
if not frappe.cache().hget("tags_count", doctype):
|
||||
tags = [tag.name for tag in frappe.get_list("Tag")]
|
||||
_user_tags = []
|
||||
for tag in tags:
|
||||
count = frappe.db.count("Tag Link", filters={"document_type": doctype, "tag": tag})
|
||||
if count > 0:
|
||||
_user_tags.append([tag, count])
|
||||
frappe.cache().hset("tags_count", doctype, _user_tags)
|
||||
if not frappe.cache().hget("Tags", doctype):
|
||||
tags = set([tag.tag for tag in frappe.get_list("Tag Link", filters={"document_type": doctype}, fields=["tag"])])
|
||||
frappe.cache().hset("Tags", doctype, tags)
|
||||
|
||||
return {"stats": {"_user_tags": frappe.cache().hget("tags_count", doctype)}}
|
||||
for tag in list(frappe.cache().hget("Tags", doctype)):
|
||||
tag_filters = []
|
||||
tag_filters.extend(filters)
|
||||
tag_filters.extend([['Tag Link', 'tag', '=', tag]])
|
||||
|
||||
count = frappe.get_all(doctype, filters=tag_filters, fields=["count(*)"])
|
||||
if count[0].get("count(*)") > 0:
|
||||
_user_tags.append([tag, count[0].get("count(*)")])
|
||||
|
||||
return {"stats": {"_user_tags": _user_tags}}
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.read_only()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ def sanitize_searchfield(searchfield):
|
|||
# this is called by the Link Field
|
||||
@frappe.whitelist()
|
||||
def search_link(doctype, txt, query=None, filters=None, page_length=20, searchfield=None, reference_doctype=None, ignore_user_permissions=False):
|
||||
search_widget(doctype, txt, query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)
|
||||
search_widget(doctype, txt.strip(), query, searchfield=searchfield, page_length=page_length, filters=filters, reference_doctype=reference_doctype, ignore_user_permissions=ignore_user_permissions)
|
||||
frappe.response['results'] = build_for_autosuggest(frappe.response["values"])
|
||||
del frappe.response["values"]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_progress_slides():
|
||||
'''
|
||||
Return user progress slides for the desktop (called via `get_user_progress_slides` hook)
|
||||
'''
|
||||
slides = []
|
||||
if cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
|
||||
for fn in frappe.get_hooks('get_user_progress_slides'):
|
||||
slides += frappe.get_attr(fn)()
|
||||
|
||||
return slides
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_and_get_user_progress():
|
||||
'''
|
||||
Return setup progress action states (called via `update_and_get_user_progress` hook)
|
||||
'''
|
||||
states = {}
|
||||
for fn in frappe.get_hooks('update_and_get_user_progress'):
|
||||
states.update(frappe.get_attr(fn)())
|
||||
|
||||
return states
|
||||
|
|
@ -21,7 +21,6 @@ from frappe.desk.form import assign_to
|
|||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils.background_jobs import enqueue, get_jobs
|
||||
from frappe.core.doctype.communication.email import set_incoming_outgoing_accounts
|
||||
from frappe.utils.scheduler import log
|
||||
from frappe.utils.html_utils import clean_email_html
|
||||
from frappe.email.utils import get_port
|
||||
|
||||
|
|
@ -284,7 +283,7 @@ class EmailAccount(Document):
|
|||
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
log('email_account.receive')
|
||||
frappe.log_error('email_account.receive')
|
||||
if self.use_imap:
|
||||
self.handle_bad_emails(email_server, uid, msg, frappe.get_traceback())
|
||||
exceptions.append(frappe.get_traceback())
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from frappe import throw, _
|
|||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from frappe.utils.verified_command import get_signed_params, verify_request
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.scheduler import log
|
||||
from frappe.email.queue import send
|
||||
from frappe.email.doctype.email_group.email_group import add_subscribers
|
||||
from frappe.utils import parse_addr
|
||||
|
|
@ -213,7 +212,7 @@ def send_newsletter(newsletter):
|
|||
doc.db_set("email_sent", 0)
|
||||
frappe.db.commit()
|
||||
|
||||
log("send_newsletter")
|
||||
frappe.log_error("send_newsletter")
|
||||
|
||||
raise
|
||||
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ class TestNotification(unittest.TestCase):
|
|||
"reference_name": event.name, "status": "Not Sent"}))
|
||||
|
||||
frappe.set_user('Administrator')
|
||||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True)
|
||||
frappe.get_doc('Scheduled Job Type', dict(method='frappe.email.doctype.notification.notification.trigger_daily_alerts')).execute()
|
||||
|
||||
# not today, so no alert
|
||||
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
|
||||
|
|
@ -150,7 +150,7 @@ class TestNotification(unittest.TestCase):
|
|||
self.assertFalse(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
|
||||
"reference_name": event.name, "status": "Not Sent"}))
|
||||
|
||||
frappe.utils.scheduler.trigger(frappe.local.site, "daily", now=True)
|
||||
frappe.get_doc('Scheduled Job Type', dict(method='frappe.email.doctype.notification.notification.trigger_daily_alerts')).execute()
|
||||
|
||||
# today so show alert
|
||||
self.assertTrue(frappe.db.get_value("Email Queue", {"reference_doctype": "Event",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from frappe.utils.verified_command import get_signed_params, verify_request
|
|||
from html2text import html2text
|
||||
from frappe.utils import get_url, nowdate, encode, now_datetime, add_days, split_emails, cstr, cint
|
||||
from rq.timeouts import JobTimeoutException
|
||||
from frappe.utils.scheduler import log
|
||||
from six import text_type, string_types
|
||||
|
||||
class EmailLimitCrossedError(frappe.ValidationError): pass
|
||||
|
|
@ -469,7 +468,7 @@ def send_one(email, smtpserver=None, auto_commit=True, now=False, from_test=Fals
|
|||
|
||||
else:
|
||||
# log to Error Log
|
||||
log('frappe.email.queue.flush', text_type(e))
|
||||
frappe.log_error('frappe.email.queue.flush')
|
||||
|
||||
def prepare_message(email, recipient, recipients_list):
|
||||
message = email.message
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import frappe
|
|||
from frappe import _, safe_decode, safe_encode
|
||||
from frappe.utils import (extract_email_id, convert_utc_to_user_timezone, now,
|
||||
cint, cstr, strip, markdown, parse_addr)
|
||||
from frappe.utils.scheduler import log
|
||||
from frappe.core.doctype.file.file import get_random_filename, MaxFileSizeReachedError
|
||||
|
||||
class EmailSizeExceededError(frappe.ValidationError): pass
|
||||
|
|
@ -80,7 +79,7 @@ class EmailServer:
|
|||
|
||||
except _socket.error:
|
||||
# log performs rollback and logs error in Error Log
|
||||
log("receive.connect_pop")
|
||||
frappe.log_error("receive.connect_pop")
|
||||
|
||||
# Invalid mail server -- due to refusing connection
|
||||
frappe.msgprint(_('Invalid Mail Server. Please rectify and try again.'))
|
||||
|
|
@ -255,7 +254,7 @@ class EmailServer:
|
|||
|
||||
else:
|
||||
# log performs rollback and logs error in Error Log
|
||||
log("receive.get_messages", self.make_error_msg(msg_num, incoming_mail))
|
||||
frappe.log_error("receive.get_messages", self.make_error_msg(msg_num, incoming_mail))
|
||||
self.errors = True
|
||||
frappe.db.rollback()
|
||||
|
||||
|
|
@ -457,9 +456,9 @@ class Email:
|
|||
def show_attached_email_headers_in_content(self, part):
|
||||
# get the multipart/alternative message
|
||||
try:
|
||||
from html import escape # python 3.x
|
||||
from html import escape # python 3.x
|
||||
except ImportError:
|
||||
from cgi import escape # python 2.x
|
||||
from cgi import escape # python 2.x
|
||||
|
||||
message = list(part.walk())[1]
|
||||
headers = []
|
||||
|
|
@ -481,7 +480,7 @@ class Email:
|
|||
"""Detect chartset."""
|
||||
charset = part.get_content_charset()
|
||||
if not charset:
|
||||
charset = chardet.detect(frappe.safe_encode(part))['encoding']
|
||||
charset = chardet.detect(cstr(part))['encoding']
|
||||
|
||||
return charset
|
||||
|
||||
|
|
@ -515,7 +514,7 @@ class Email:
|
|||
'fcontent': fcontent,
|
||||
})
|
||||
|
||||
cid = (part.get("Content-Id") or "").strip("><")
|
||||
cid = (cstr(part.get("Content-Id")) or "").strip("><")
|
||||
if cid:
|
||||
self.cid_map[fname] = cid
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,5 @@ frappe.ui.form.on('Currency', {
|
|||
if(!frm.doc.enabled) {
|
||||
frm.set_intro(__("This Currency is disabled. Enable to use in transactions"));
|
||||
}
|
||||
},
|
||||
|
||||
after_save(frm) {
|
||||
if (frm.doc.enabled)
|
||||
locals[':Currency'][frm.doc.name] = Object.assign(frm.doc, { doctype: ':Currency' });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -76,8 +76,7 @@ leaderboards = "frappe.desk.leaderboard.get_leaderboards"
|
|||
|
||||
on_session_creation = [
|
||||
"frappe.core.doctype.activity_log.feed.login_feed",
|
||||
"frappe.core.doctype.user.user.notify_admin_access_to_system_manager",
|
||||
"frappe.utils.scheduler.reset_enabled_scheduler_events",
|
||||
"frappe.core.doctype.user.user.notify_admin_access_to_system_manager"
|
||||
]
|
||||
|
||||
on_logout = "frappe.core.doctype.session_default_settings.session_default_settings.clear_session_defaults"
|
||||
|
|
@ -153,14 +152,18 @@ doc_events = {
|
|||
}
|
||||
|
||||
scheduler_events = {
|
||||
"cron": {
|
||||
"0/15 * * * *": [
|
||||
"frappe.oauth.delete_oauth2_data",
|
||||
"frappe.website.doctype.web_page.web_page.check_publish_status",
|
||||
"frappe.twofactor.delete_all_barcodes_for_users"
|
||||
]
|
||||
},
|
||||
"all": [
|
||||
"frappe.email.queue.flush",
|
||||
"frappe.email.doctype.email_account.email_account.pull",
|
||||
"frappe.email.doctype.email_account.email_account.notify_unreplied",
|
||||
"frappe.oauth.delete_oauth2_data",
|
||||
"frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment",
|
||||
"frappe.twofactor.delete_all_barcodes_for_users",
|
||||
"frappe.website.doctype.web_page.web_page.check_publish_status",
|
||||
'frappe.utils.global_search.sync_global_search'
|
||||
],
|
||||
"hourly": [
|
||||
|
|
|
|||
|
|
@ -18,15 +18,16 @@ from frappe.utils.fixtures import sync_fixtures
|
|||
from frappe.website import render
|
||||
from frappe.modules.utils import sync_customizations
|
||||
from frappe.database import setup_database
|
||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
|
||||
|
||||
def install_db(root_login="root", root_password=None, db_name=None, source_sql=None,
|
||||
admin_password=None, verbose=True, force=0, site_config=None, reinstall=False,
|
||||
db_type=None):
|
||||
admin_password=None, verbose=True, force=0, site_config=None, reinstall=False,
|
||||
db_type=None, db_host=None, db_port=None):
|
||||
|
||||
if not db_type:
|
||||
db_type = frappe.conf.db_type or 'mariadb'
|
||||
|
||||
make_conf(db_name, site_config=site_config, db_type=db_type)
|
||||
make_conf(db_name, site_config=site_config, db_type=db_type, db_host=db_host, db_port=db_port)
|
||||
frappe.flags.in_install_db = True
|
||||
|
||||
frappe.flags.root_login = root_login
|
||||
|
|
@ -91,6 +92,7 @@ def install_app(name, verbose=False, set_as_patched=True):
|
|||
for after_install in app_hooks.after_install or []:
|
||||
frappe.get_attr(after_install)()
|
||||
|
||||
sync_jobs()
|
||||
sync_fixtures(name)
|
||||
sync_customizations(name)
|
||||
|
||||
|
|
@ -189,14 +191,14 @@ def init_singles():
|
|||
doc.flags.ignore_validate=True
|
||||
doc.save()
|
||||
|
||||
def make_conf(db_name=None, db_password=None, site_config=None, db_type=None):
|
||||
def make_conf(db_name=None, db_password=None, site_config=None, db_type=None, db_host=None, db_port=None):
|
||||
site = frappe.local.site
|
||||
make_site_config(db_name, db_password, site_config, db_type=db_type)
|
||||
make_site_config(db_name, db_password, site_config, db_type=db_type, db_host=db_host, db_port=db_port)
|
||||
sites_path = frappe.local.sites_path
|
||||
frappe.destroy()
|
||||
frappe.init(site, sites_path=sites_path)
|
||||
|
||||
def make_site_config(db_name=None, db_password=None, site_config=None, db_type=None):
|
||||
def make_site_config(db_name=None, db_password=None, site_config=None, db_type=None, db_host=None, db_port=None):
|
||||
frappe.create_folder(os.path.join(frappe.local.site_path))
|
||||
site_file = get_site_config_path()
|
||||
|
||||
|
|
@ -207,6 +209,12 @@ def make_site_config(db_name=None, db_password=None, site_config=None, db_type=N
|
|||
if db_type:
|
||||
site_config['db_type'] = db_type
|
||||
|
||||
if db_host:
|
||||
site_config['db_host'] = db_host
|
||||
|
||||
if db_port:
|
||||
site_config['db_port'] = db_port
|
||||
|
||||
with open(site_file, "w") as f:
|
||||
f.write(json.dumps(site_config, indent=1, sort_keys=True))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,700 +1,183 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-11-18 15:36:09.676722",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"allow_import": 1,
|
||||
"creation": "2017-11-18 15:36:09.676722",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enable_social_login",
|
||||
"client_credentials",
|
||||
"social_login_provider",
|
||||
"client_id",
|
||||
"column_break_0",
|
||||
"provider_name",
|
||||
"client_secret",
|
||||
"sb_identity_details",
|
||||
"icon",
|
||||
"column_break_1",
|
||||
"base_url",
|
||||
"client_urls",
|
||||
"authorize_url",
|
||||
"access_token_url",
|
||||
"column_break_3",
|
||||
"redirect_url",
|
||||
"api_endpoint",
|
||||
"custom_base_url",
|
||||
"client_information",
|
||||
"api_endpoint_args",
|
||||
"auth_url_data",
|
||||
"user_id_property"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "enable_social_login",
|
||||
"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": "Enable Social Login",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "enable_social_login",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Social Login"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.enable_social_login",
|
||||
"columns": 0,
|
||||
"fieldname": "client_credentials",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Client Credentials",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.enable_social_login",
|
||||
"fieldname": "client_credentials",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Client Credentials"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Custom",
|
||||
"depends_on": "eval:doc.custom!=1",
|
||||
"fieldname": "social_login_provider",
|
||||
"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": "Social Login Provider",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Custom\nFacebook\nFrappe\nGitHub\nGoogle\nOffice 365\nSalesforce\nfairlogin",
|
||||
"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": 1,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "Custom",
|
||||
"depends_on": "eval:doc.custom!=1",
|
||||
"fieldname": "social_login_provider",
|
||||
"fieldtype": "Select",
|
||||
"label": "Social Login Provider",
|
||||
"options": "Custom\nFacebook\nFrappe\nGitHub\nGoogle\nOffice 365\nSalesforce\nfairlogin",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client_id",
|
||||
"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": "Client ID",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "client_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Client ID"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_0",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_0",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "provider_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Provider Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "provider_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Provider Name",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client_secret",
|
||||
"fieldtype": "Password",
|
||||
"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": "Client Secret",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "client_secret",
|
||||
"fieldtype": "Password",
|
||||
"label": "Client Secret"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.custom_base_url",
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "sb_identity_details",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Identity Details",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.custom_base_url",
|
||||
"fieldname": "sb_identity_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Identity Details"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "icon",
|
||||
"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": "Icon",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "icon",
|
||||
"fieldtype": "Data",
|
||||
"label": "Icon"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "base_url",
|
||||
"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": "Base URL",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "base_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Base URL"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.social_login_provider===\"Custom\"",
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "client_urls",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Client URLs",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.social_login_provider===\"Custom\"",
|
||||
"fieldname": "client_urls",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Client URLs"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "authorize_url",
|
||||
"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": "Authorize URL",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "authorize_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Authorize URL"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "access_token_url",
|
||||
"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": "Access Token URL",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "access_token_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Access Token URL"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 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,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "redirect_url",
|
||||
"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": "Redirect URL",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "redirect_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Redirect URL"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "api_endpoint",
|
||||
"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": "API Endpoint",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "api_endpoint",
|
||||
"fieldtype": "Data",
|
||||
"label": "API Endpoint"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "custom_base_url",
|
||||
"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": "Custom Base URL",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "custom_base_url",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Custom Base URL"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.social_login_provider===\"Custom\"",
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "client_information",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Client Information",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.social_login_provider===\"Custom\"",
|
||||
"fieldname": "client_information",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Client Information"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "api_endpoint_args",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "API Endpoint Args",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "api_endpoint_args",
|
||||
"fieldtype": "Code",
|
||||
"label": "API Endpoint Args"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "auth_url_data",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Auth URL Data",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "auth_url_data",
|
||||
"fieldtype": "Code",
|
||||
"label": "Auth URL Data"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.social_login_provider===\"Custom\"",
|
||||
"fieldname": "user_id_property",
|
||||
"fieldtype": "Data",
|
||||
"label": "User ID Property"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-09-15 09:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Social Login Key",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"modified": "2019-12-03 12:35:55.115260",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Social Login Key",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "provider_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "provider_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ from frappe.desk.notifications import clear_notifications
|
|||
from frappe.website import render
|
||||
from frappe.core.doctype.language.language import sync_languages
|
||||
from frappe.modules.utils import sync_customizations
|
||||
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
|
||||
from frappe.utils import global_search
|
||||
|
||||
def migrate(verbose=True, rebuild_website=False, skip_failing=False):
|
||||
|
|
@ -46,9 +47,11 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False):
|
|||
|
||||
# run patches
|
||||
frappe.modules.patch_handler.run_all(skip_failing)
|
||||
|
||||
# sync
|
||||
frappe.model.sync.sync_all(verbose=verbose)
|
||||
frappe.translate.clear_cache()
|
||||
sync_jobs()
|
||||
sync_fixtures()
|
||||
sync_customizations()
|
||||
sync_languages()
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ default_fields = ('doctype','name','owner','creation','modified','modified_by',
|
|||
'parent','parentfield','parenttype','idx','docstatus')
|
||||
optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by", "_seen")
|
||||
table_fields = ('Table', 'Table MultiSelect')
|
||||
core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'User', 'Role', 'Has Role',
|
||||
core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'DocType Action', 'DocType Link', 'User', 'Role', 'Has Role',
|
||||
'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form',
|
||||
'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script')
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ max_positive_value = {
|
|||
'bigint': 2 ** 63
|
||||
}
|
||||
|
||||
DOCTYPES_FOR_DOCTYPE = ('DocType', 'DocField', 'DocPerm', 'DocType Action', 'DocType Link')
|
||||
|
||||
_classes = {}
|
||||
|
||||
def get_controller(doctype):
|
||||
|
|
@ -255,7 +257,7 @@ class BaseDocument(object):
|
|||
|
||||
def get_valid_columns(self):
|
||||
if self.doctype not in frappe.local.valid_columns:
|
||||
if self.doctype in ("DocField", "DocPerm") and self.parent in ("DocType", "DocField", "DocPerm"):
|
||||
if self.doctype in DOCTYPES_FOR_DOCTYPE:
|
||||
from frappe.model.meta import get_table_columns
|
||||
valid = get_table_columns(self.doctype)
|
||||
else:
|
||||
|
|
@ -312,7 +314,7 @@ class BaseDocument(object):
|
|||
self.created_by = self.modified_by = frappe.session.user
|
||||
|
||||
# if doctype is "DocType", don't insert null values as we don't know who is valid yet
|
||||
d = self.get_valid_dict(convert_dates_to_str=True, ignore_nulls = self.doctype in ('DocType', 'DocField', 'DocPerm'))
|
||||
d = self.get_valid_dict(convert_dates_to_str=True, ignore_nulls = self.doctype in DOCTYPES_FOR_DOCTYPE)
|
||||
|
||||
columns = list(d)
|
||||
try:
|
||||
|
|
@ -347,7 +349,7 @@ class BaseDocument(object):
|
|||
self.db_insert()
|
||||
return
|
||||
|
||||
d = self.get_valid_dict(convert_dates_to_str=True, ignore_nulls = self.doctype in ('DocType', 'DocField', 'DocPerm'))
|
||||
d = self.get_valid_dict(convert_dates_to_str=True, ignore_nulls = self.doctype in DOCTYPES_FOR_DOCTYPE)
|
||||
|
||||
# don't update name, as case might've been changed
|
||||
name = d['name']
|
||||
|
|
|
|||
|
|
@ -501,6 +501,10 @@ class DatabaseQuery(object):
|
|||
value = f.value or "''"
|
||||
fallback = "''"
|
||||
|
||||
elif f.fieldname == 'name':
|
||||
value = f.value or "''"
|
||||
fallback = "''"
|
||||
|
||||
else:
|
||||
value = flt(f.value)
|
||||
fallback = 0
|
||||
|
|
@ -513,6 +517,8 @@ class DatabaseQuery(object):
|
|||
or not can_be_null
|
||||
or (f.value and f.operator.lower() in ('=', 'like'))
|
||||
or 'ifnull(' in column_name.lower()):
|
||||
if f.operator.lower() == 'like' and frappe.conf.get('db_type') == 'postgres':
|
||||
f.operator = 'ilike'
|
||||
condition = '{column_name} {operator} {value}'.format(
|
||||
column_name=column_name, operator=f.operator,
|
||||
value=value)
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa
|
|||
if not (for_reload or frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_test):
|
||||
try:
|
||||
delete_controllers(name, doc.module)
|
||||
except (FileNotFoundError, OSError):
|
||||
# in case a doctype doesnt have any controller code
|
||||
except (FileNotFoundError, OSError, KeyError):
|
||||
# in case a doctype doesnt have any controller code nor any app and module
|
||||
pass
|
||||
|
||||
else:
|
||||
|
|
@ -332,8 +332,8 @@ def clear_references(doctype, reference_doctype, reference_name,
|
|||
(reference_doctype, reference_name))
|
||||
|
||||
def clear_timeline_references(link_doctype, link_name):
|
||||
frappe.db.sql("""delete from `tabCommunication Link`
|
||||
where `tabCommunication Link`.link_doctype='{0}' and `tabCommunication Link`.link_name='{1}'""".format(link_doctype, link_name)) # nosec
|
||||
frappe.db.sql("""DELETE FROM `tabCommunication Link`
|
||||
WHERE `tabCommunication Link`.link_doctype=%s AND `tabCommunication Link`.link_name=%s""", (link_doctype, link_name))
|
||||
|
||||
def insert_feed(doc):
|
||||
from frappe.utils import get_fullname
|
||||
|
|
|
|||
|
|
@ -150,8 +150,8 @@ class Document(BaseDocument):
|
|||
super(Document, self).__init__(d)
|
||||
|
||||
if self.name=="DocType" and self.doctype=="DocType":
|
||||
from frappe.model.meta import doctype_table_fields
|
||||
table_fields = doctype_table_fields
|
||||
from frappe.model.meta import DOCTYPE_TABLE_FIELDS
|
||||
table_fields = DOCTYPE_TABLE_FIELDS
|
||||
else:
|
||||
table_fields = self.meta.get_table_fields()
|
||||
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class Meta(Document):
|
|||
if self.name!="DocType":
|
||||
self._table_fields = self.get('fields', {"fieldtype": ['in', table_fields]})
|
||||
else:
|
||||
self._table_fields = doctype_table_fields
|
||||
self._table_fields = DOCTYPE_TABLE_FIELDS
|
||||
|
||||
return self._table_fields
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ class Meta(Document):
|
|||
|
||||
def get_valid_columns(self):
|
||||
if not hasattr(self, "_valid_columns"):
|
||||
if self.name in ("DocType", "DocField", "DocPerm", "Property Setter"):
|
||||
if self.name in ("DocType", "DocField", "DocPerm", 'DocType Action', 'DocType Link', "Property Setter"):
|
||||
self._valid_columns = get_table_columns(self.name)
|
||||
else:
|
||||
self._valid_columns = self.default_fields + \
|
||||
|
|
@ -174,7 +174,12 @@ class Meta(Document):
|
|||
return self._valid_columns
|
||||
|
||||
def get_table_field_doctype(self, fieldname):
|
||||
return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname)
|
||||
return {
|
||||
"fields": "DocField",
|
||||
"permissions": "DocPerm",
|
||||
"actions": "DocType Action",
|
||||
'links': 'DocType Link'
|
||||
}.get(fieldname)
|
||||
|
||||
def get_field(self, fieldname):
|
||||
'''Return docfield from meta'''
|
||||
|
|
@ -419,11 +424,44 @@ class Meta(Document):
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
self.add_doctype_links(data)
|
||||
|
||||
for hook in frappe.get_hooks("override_doctype_dashboards", {}).get(self.name, []):
|
||||
data = frappe.get_attr(hook)(data=data)
|
||||
|
||||
return data
|
||||
|
||||
def add_doctype_links(self, data):
|
||||
'''add `links` child table in standard link dashboard format'''
|
||||
if hasattr(self, 'links') and self.links:
|
||||
if not data.transactions:
|
||||
# init groups
|
||||
data.transactions = []
|
||||
data.non_standard_fieldnames = {}
|
||||
|
||||
for link in self.links:
|
||||
link.added = False
|
||||
for group in data.transactions:
|
||||
# group found
|
||||
if group.label == link.label:
|
||||
if not link.link_doctype in group.items:
|
||||
group.items.append(link.link_doctype)
|
||||
link.added = True
|
||||
|
||||
if not link.added:
|
||||
# group not found, make a new group
|
||||
data.transactions.append(dict(
|
||||
label = link.group,
|
||||
items = [link.link_doctype]
|
||||
))
|
||||
|
||||
if link.link_fieldname != data.fieldname:
|
||||
if data.fieldname:
|
||||
data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname
|
||||
else:
|
||||
data.fieldname = link.link_fieldname
|
||||
|
||||
|
||||
def get_row_template(self):
|
||||
return self.get_web_template(suffix='_row')
|
||||
|
||||
|
|
@ -441,9 +479,11 @@ class Meta(Document):
|
|||
def is_nested_set(self):
|
||||
return self.has_field('lft') and self.has_field('rgt')
|
||||
|
||||
doctype_table_fields = [
|
||||
DOCTYPE_TABLE_FIELDS = [
|
||||
frappe._dict({"fieldname": "fields", "options": "DocField"}),
|
||||
frappe._dict({"fieldname": "permissions", "options": "DocPerm"})
|
||||
frappe._dict({"fieldname": "permissions", "options": "DocPerm"}),
|
||||
frappe._dict({"fieldname": "actions", "options": "DocType Action"}),
|
||||
frappe._dict({"fieldname": "links", "options": "DocType Link"}),
|
||||
]
|
||||
|
||||
#######
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe
|
|||
# these need to go first at time of install
|
||||
for d in (("core", "docfield"),
|
||||
("core", "docperm"),
|
||||
("core", "doctype_action"),
|
||||
("core", "doctype_link"),
|
||||
("core", "role"),
|
||||
("core", "has_role"),
|
||||
("core", "doctype"),
|
||||
|
|
@ -41,7 +43,10 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe
|
|||
("data_migration", "data_migration_mapping_detail"),
|
||||
("data_migration", "data_migration_mapping"),
|
||||
("data_migration", "data_migration_plan_mapping"),
|
||||
("data_migration", "data_migration_plan")):
|
||||
("data_migration", "data_migration_plan"),
|
||||
("desk", "onboarding_slide_field"),
|
||||
("desk", "onboarding_slide_help_link"),
|
||||
("desk", "onboarding_slide")):
|
||||
files.append(os.path.join(frappe.get_app_path("frappe"), d[0],
|
||||
"doctype", d[1], d[1] + ".json"))
|
||||
|
||||
|
|
@ -70,7 +75,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F
|
|||
# load in sequence - warning for devs
|
||||
document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format',
|
||||
'website_theme', 'web_form', 'notification', 'print_style',
|
||||
'data_migration_mapping', 'data_migration_plan']
|
||||
'data_migration_mapping', 'data_migration_plan', 'onboarding_slide']
|
||||
for doctype in document_types:
|
||||
doctype_path = os.path.join(start_path, doctype)
|
||||
if os.path.exists(doctype_path):
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ frappe.patches.v8_0.update_global_search_table
|
|||
frappe.patches.v7_0.update_auth
|
||||
frappe.patches.v8_0.drop_in_dialog #2017-09-22
|
||||
frappe.patches.v7_2.remove_in_filter
|
||||
frappe.patches.v11_0.drop_column_apply_user_permissions
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype_action', force=True) #2019-09-23
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype_link', force=True) #2019-09-23
|
||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2018-02-20
|
||||
frappe.patches.v11_0.drop_column_apply_user_permissions
|
||||
execute:frappe.reload_doc('core', 'doctype', 'custom_docperm')
|
||||
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29
|
||||
execute:frappe.reload_doc('core', 'doctype', 'comment')
|
||||
|
|
@ -257,3 +259,7 @@ 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
|
||||
frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26
|
||||
execute:frappe.delete_doc("Test Runner")
|
||||
frappe.patches.v12_0.setup_email_linking
|
||||
frappe.patches.v12_0.fix_home_settings_for_all_users
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.count("File", filters={"attached_to_doctype": "Prepared Report", "is_private": 0}) > 10000:
|
||||
frappe.db.auto_commit_on_many_writes = True
|
||||
|
||||
files = frappe.get_all("File", fields=["name", "attached_to_name"], filters={"attached_to_doctype": "Prepared Report", "is_private": 0})
|
||||
for file_dict in files:
|
||||
# For some reason Prepared Report doc might not exist, check if it exists first
|
||||
if frappe.db.exists("Prepared Report", file_dict.attached_to_name):
|
||||
try:
|
||||
file_doc = frappe.get_doc("File", file_dict.name)
|
||||
file_doc.is_private = 1
|
||||
file_doc.save()
|
||||
except Exception:
|
||||
# File might not exist on the file system in that case delete both Prepared Report and File doc
|
||||
frappe.delete_doc("Prepared Report", file_dict.attached_to_name)
|
||||
else:
|
||||
# If Prepared Report doc doesn't exist then the file doc is useless. Delete it.
|
||||
frappe.delete_doc("File", file_dict.name)
|
||||
|
||||
if frappe.db.auto_commit_on_many_writes:
|
||||
frappe.db.auto_commit_on_many_writes = False
|
||||
|
||||
41
frappe/patches/v12_0/fix_home_settings_for_all_users.py
Normal file
41
frappe/patches/v12_0/fix_home_settings_for_all_users.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import frappe
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
import json
|
||||
def execute():
|
||||
users = frappe.get_all('User', fields=['name', 'home_settings'])
|
||||
|
||||
for user in users:
|
||||
|
||||
if not user.home_settings:
|
||||
continue
|
||||
|
||||
home_settings = json.loads(user.home_settings)
|
||||
|
||||
modules_by_category = home_settings.get('modules_by_category')
|
||||
if not modules_by_category:
|
||||
continue
|
||||
visible_modules = []
|
||||
category_to_check = []
|
||||
|
||||
for category, modules in modules_by_category.items():
|
||||
visible_modules += modules
|
||||
category_to_check.append(category)
|
||||
|
||||
all_modules = get_modules_from_all_apps_for_user(user.name)
|
||||
all_modules = set([m.get('name') or m.get('module_name') or m.get('label') \
|
||||
for m in all_modules if m.get('category') in category_to_check])
|
||||
|
||||
hidden_modules = home_settings['hidden_modules']
|
||||
|
||||
modules_in_home_settings = set(visible_modules + hidden_modules)
|
||||
|
||||
all_modules = all_modules.union(modules_in_home_settings)
|
||||
|
||||
missing_modules = all_modules - modules_in_home_settings
|
||||
|
||||
if missing_modules:
|
||||
home_settings['hidden_modules'] += missing_modules
|
||||
home_settings = json.dumps(home_settings)
|
||||
frappe.set_value('User', user.name, 'home_settings', home_settings)
|
||||
|
||||
frappe.cache().delete_key('home_settings')
|
||||
6
frappe/patches/v12_0/setup_email_linking.py
Normal file
6
frappe/patches/v12_0/setup_email_linking.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from frappe.desk.page.setup_wizard.install_fixtures import setup_email_linking
|
||||
|
||||
def execute():
|
||||
setup_email_linking()
|
||||
|
|
@ -4,8 +4,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import is_image
|
||||
|
||||
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
class LetterHead(Document):
|
||||
|
|
@ -43,3 +42,16 @@ class LetterHead(Document):
|
|||
|
||||
# update control panel - so it loads new letter directly
|
||||
frappe.db.set_default("default_letter_head_content", self.content)
|
||||
|
||||
def create_onboarding_docs(self, args):
|
||||
letterhead = args.get('letterhead')
|
||||
if letterhead:
|
||||
try:
|
||||
frappe.get_doc({
|
||||
'doctype': self.doctype,
|
||||
'image': letterhead,
|
||||
'letter_head_name': _('Standard'),
|
||||
'is_default': 1
|
||||
}).insert()
|
||||
except frappe.NameError:
|
||||
pass
|
||||
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue