Merge branch develop into remove_user_limit
This commit is contained in:
commit
87e3531281
248 changed files with 10714 additions and 10465 deletions
13
.mergify.yml
Normal file
13
.mergify.yml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
pull_request_rules:
|
||||
- name: Automatic merge on CI success and review
|
||||
conditions:
|
||||
- status-success=Codacy/PR Quality Review
|
||||
- status-success=Semantic Pull Request
|
||||
- status-success=continuous-integration/travis-ci/pr
|
||||
- status-success=security/snyk - package.json (frappe)
|
||||
- status-success=security/snyk - requirements.txt (frappe)
|
||||
- label!=don't-merge
|
||||
- "#approved-reviews-by>=1"
|
||||
actions:
|
||||
merge:
|
||||
method: merge
|
||||
103
.travis.yml
103
.travis.yml
|
|
@ -2,43 +2,88 @@ language: python
|
|||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
python:
|
||||
- 2.7
|
||||
- 3.6
|
||||
|
||||
env:
|
||||
- DB=mariadb
|
||||
- DB=postgres
|
||||
- TEST_TYPE=ui
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
addons:
|
||||
postgresql: "9.5"
|
||||
hosts:
|
||||
- test_site
|
||||
- test_site_postgres
|
||||
- test_site_ui
|
||||
mariadb: 10.3
|
||||
postgresql: 9.5
|
||||
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
cache:
|
||||
- pip
|
||||
- npm
|
||||
- yarn
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- python: 2.7
|
||||
env: DB=postgres
|
||||
- python: 2.7
|
||||
env: TEST_TYPE=ui
|
||||
include:
|
||||
- name: "Python 3.6 MariaDB"
|
||||
python: 3.6
|
||||
env: DB=mariadb TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
|
||||
- name: "Python 3.6 PostgreSQL"
|
||||
python: 3.6
|
||||
env: DB=postgres TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
|
||||
- name: "Cypress"
|
||||
python: 3.6
|
||||
env: DB=mariadb TYPE=ui
|
||||
before_script: bench --site test_site execute frappe.utils.install.complete_setup_wizard
|
||||
script: bench --site test_site run-ui-tests frappe --headless
|
||||
|
||||
- name: "Python 2.7 MariaDB"
|
||||
python: 2.7
|
||||
env: DB=mariadb TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
|
||||
install:
|
||||
- $TRAVIS_BUILD_DIR/.travis/install.sh
|
||||
- cd ~
|
||||
- source ./.nvm/nvm.sh
|
||||
- nvm install v8.10.0
|
||||
|
||||
- git clone https://github.com/frappe/bench --depth 1
|
||||
- pip install -e ./bench
|
||||
|
||||
- bench init frappe-bench --skip-assets --python $(which python) --frappe-path $TRAVIS_BUILD_DIR
|
||||
|
||||
- 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'"
|
||||
|
||||
- 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
|
||||
|
||||
- cd ./frappe-bench
|
||||
|
||||
- sed -i 's/watch:/# watch:/g' Procfile
|
||||
- sed -i 's/schedule:/# schedule:/g' Procfile
|
||||
|
||||
- if [ $TYPE == "server" ]; then sed -i 's/socketio:/# socketio:/g' Procfile; fi
|
||||
- if [ $TYPE == "server" ]; then sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile; fi
|
||||
|
||||
- if [ $TYPE == "ui" ]; then bench setup requirements --node; fi
|
||||
|
||||
before_script:
|
||||
- cd ~/frappe-bench
|
||||
- sed -i 's/9000/9001/g' sites/common_site_config.json
|
||||
- bench start &
|
||||
- sleep 10
|
||||
|
||||
script:
|
||||
- $TRAVIS_BUILD_DIR/.travis/run-tests.sh
|
||||
- bench --site test_site reinstall --yes
|
||||
- bench build --app frappe
|
||||
|
||||
after_script:
|
||||
- coveralls -b apps/frappe -d ../../sites/.coverage
|
||||
- pip install python-coveralls
|
||||
- coveralls -b apps/frappe -d ../../sites/.coverage
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
sudo rm /etc/apt/sources.list.d/mongodb*.list
|
||||
sudo rm /etc/apt/sources.list.d/docker.list
|
||||
sudo apt-get install hhvm && rm -rf /home/travis/.kiex/
|
||||
sudo apt-get purge -y mysql-common mysql-server mysql-client
|
||||
source ~/.nvm/nvm.sh
|
||||
nvm install v8.10.0
|
||||
|
||||
pip install python-coveralls
|
||||
|
||||
wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
|
||||
|
||||
sudo python install.py --develop --user travis --without-bench-setup
|
||||
sudo pip install -e ~/bench
|
||||
|
||||
rm $TRAVIS_BUILD_DIR/.git/shallow
|
||||
cd ~/ && bench init frappe-bench --python $(which python) --frappe-path $TRAVIS_BUILD_DIR
|
||||
if [[ $DB == 'mariadb' ]]; then
|
||||
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
|
||||
elif [[ $TEST_TYPE == 'ui' ]]; then
|
||||
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_ui ~/frappe-bench/sites/
|
||||
elif [[ $DB == 'postgres' ]]; then
|
||||
cp -r $TRAVIS_BUILD_DIR/test_sites/test_site_postgres ~/frappe-bench/sites/
|
||||
fi
|
||||
14
.travis/mariadb.json
Normal file
14
.travis/mariadb.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_host": "localhost",
|
||||
"db_name": "test_frappe",
|
||||
"db_password": "test_frappe",
|
||||
"db_type": "mariadb",
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
"mail_login": "test@example.com",
|
||||
"mail_password": "test",
|
||||
"admin_password": "admin",
|
||||
"root_login": "root",
|
||||
"root_password": "travis",
|
||||
"host_name": "http://test_site:8000"
|
||||
}
|
||||
14
.travis/postgres.json
Normal file
14
.travis/postgres.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"db_host": "localhost",
|
||||
"db_name": "test_frappe",
|
||||
"db_password": "test_frappe",
|
||||
"db_type": "postgres",
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
"mail_login": "test@example.com",
|
||||
"mail_password": "test",
|
||||
"admin_password": "admin",
|
||||
"root_login": "postgres",
|
||||
"root_password": "travis",
|
||||
"host_name": "http://test_site:8000"
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
setup_mariadb_env() {
|
||||
mysql -u root -ptravis -e "create database $1"
|
||||
mysql -u root -ptravis -e "USE mysql; CREATE USER '$1'@'localhost' IDENTIFIED BY '$1'; FLUSH PRIVILEGES; "
|
||||
mysql -u root -ptravis -e "USE mysql; GRANT ALL PRIVILEGES ON \`$1\`.* TO '$1'@'localhost';"
|
||||
}
|
||||
|
||||
if [[ $DB == 'mariadb' ]]; then
|
||||
setup_mariadb_env 'test_frappe'
|
||||
bench --site test_site reinstall --yes
|
||||
bench --site test_site scheduler disable
|
||||
bench --site test_site run-tests --coverage
|
||||
|
||||
elif [[ $TEST_TYPE == 'ui' ]]; then
|
||||
setup_mariadb_env 'test_site_ui'
|
||||
bench --site test_site_ui reinstall --yes
|
||||
bench --site test_site_ui execute frappe.utils.install.complete_setup_wizard
|
||||
bench --site test_site_ui scheduler disable
|
||||
cd apps/frappe && yarn && yarn cypress:run
|
||||
|
||||
elif [[ $DB == 'postgres' ]]; then
|
||||
psql -c "CREATE DATABASE test_frappe;" -U postgres
|
||||
psql -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe';" -U postgres
|
||||
bench --site test_site_postgres reinstall --yes
|
||||
bench --site test_site_postgres scheduler disable
|
||||
bench --site test_site_postgres run-tests --coverage
|
||||
fi
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"baseUrl": "http://test_site_ui:8000",
|
||||
"projectId": "92odwv"
|
||||
"projectId": "92odwv",
|
||||
"adminPassword": "admin"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Awesome Bar', () => {
|
||||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
|
|
@ -10,8 +10,9 @@ context('Awesome Bar', () => {
|
|||
});
|
||||
|
||||
it('navigates to doctype list', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('todo{downarrow}{enter}', { delay: 100 });
|
||||
cy.get('#navbar-search').type('todo', { delay: 200 });
|
||||
cy.get('#navbar-search + ul').should('be.visible');
|
||||
cy.get('#navbar-search').type('{downarrow}{enter}', { delay: 100 });
|
||||
|
||||
cy.get('h1').should('contain', 'To Do');
|
||||
|
||||
|
|
@ -20,7 +21,7 @@ context('Awesome Bar', () => {
|
|||
|
||||
it('find text in doctype list', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('test in todo{downarrow}{enter}', { delay: 100 });
|
||||
.type('test in todo{downarrow}{enter}', { delay: 200 });
|
||||
|
||||
cy.get('h1').should('contain', 'To Do');
|
||||
|
||||
|
|
@ -31,14 +32,14 @@ context('Awesome Bar', () => {
|
|||
|
||||
it('navigates to new form', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('new blog post{downarrow}{enter}', { delay: 100 });
|
||||
.type('new blog post{downarrow}{enter}', { delay: 200 });
|
||||
|
||||
cy.get('.title-text:visible').should('have.text', 'New Blog Post 1');
|
||||
});
|
||||
|
||||
it('calculates math expressions', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('55 + 32{downarrow}{enter}', { delay: 100 });
|
||||
.type('55 + 32{downarrow}{enter}', { delay: 200 });
|
||||
|
||||
cy.get('.modal-title').should('contain', 'Result');
|
||||
cy.get('.msgprint').should('contain', '55 + 32 = 87');
|
||||
|
|
|
|||
75
cypress/integration/control_link.js
Normal file
75
cypress/integration/control_link.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
context('Control Link', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.create_records({
|
||||
doctype: 'ToDo',
|
||||
description: 'this is a test todo for link'
|
||||
}).as('todos');
|
||||
});
|
||||
|
||||
function get_dialog_with_link() {
|
||||
return cy.dialog({
|
||||
title: 'Link',
|
||||
fields: [
|
||||
{
|
||||
'label': 'Select ToDo',
|
||||
'fieldname': 'link',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'ToDo'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
it('should set the valid value', () => {
|
||||
get_dialog_with_link().as('dialog');
|
||||
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=link] input')
|
||||
.focus()
|
||||
.type('todo for li')
|
||||
.type('n', { delay: 600 })
|
||||
.type('k', { delay: 700 });
|
||||
cy.wait('@search_link');
|
||||
cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
|
||||
cy.get('.frappe-control[data-fieldname=link] input').type('{downarrow}{enter}', { delay: 100 });
|
||||
cy.get('.frappe-control[data-fieldname=link] input').blur();
|
||||
cy.get('@dialog').then(dialog => {
|
||||
cy.get('@todos').then(todos => {
|
||||
let value = dialog.get_value('link');
|
||||
expect(value).to.eq(todos[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.only('should unset invalid value', () => {
|
||||
get_dialog_with_link().as('dialog');
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=link] input')
|
||||
.type('invalid value', { delay: 100 })
|
||||
.blur();
|
||||
cy.wait('@validate_link');
|
||||
cy.get('.frappe-control[data-fieldname=link] input').should('have.value', '');
|
||||
});
|
||||
|
||||
it('should route to form on arrow click', () => {
|
||||
get_dialog_with_link().as('dialog');
|
||||
|
||||
cy.server();
|
||||
cy.route('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
|
||||
|
||||
cy.get('@todos').then(todos => {
|
||||
cy.get('.frappe-control[data-fieldname=link] input').type(todos[0]).blur();
|
||||
cy.wait('@validate_link');
|
||||
cy.get('.frappe-control[data-fieldname=link] input').focus();
|
||||
cy.get('.frappe-control[data-fieldname=link] .link-btn').click();
|
||||
cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,21 @@
|
|||
context('Rating Control', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
context('Control Rating', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
function get_dialog_with_rating() {
|
||||
return cy.dialog({
|
||||
title: 'Rating',
|
||||
fields: [{
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Rating',
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
it('click on the star rating to record value', () => {
|
||||
cy.visit('/desk');
|
||||
cy.dialog('Rating', [{
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Rating',
|
||||
}]).as('dialog');
|
||||
get_dialog_with_rating().as('dialog');
|
||||
|
||||
cy.get('div.rating')
|
||||
.children('i.fa')
|
||||
|
|
@ -18,15 +25,13 @@ context('Rating Control', () => {
|
|||
cy.get('@dialog').then(dialog => {
|
||||
var value = dialog.get_value('rate');
|
||||
expect(value).to.equal(1);
|
||||
dialog.hide();
|
||||
});
|
||||
});
|
||||
|
||||
it('hover on the star', () => {
|
||||
cy.visit('/desk');
|
||||
cy.dialog('Rating', [{
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Rating',
|
||||
}]);
|
||||
get_dialog_with_rating();
|
||||
|
||||
cy.get('div.rating')
|
||||
.children('i.fa')
|
||||
.first()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
context('FileUploader', () => {
|
||||
before(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
context('List View', () => {
|
||||
before(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.test_utils.setup_workflow");
|
||||
frappe.call("frappe.tests.ui_test_helpers.setup_workflow");
|
||||
});
|
||||
cy.clear_cache();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
context('List View Settings', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
it('Default settings', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('.sidebar-stat').should('contain', "No Tags");
|
||||
cy.get('.sidebar-stat').should('contain', "Tags");
|
||||
});
|
||||
it('disable count and sidebar stats then verify', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
|
|
@ -14,13 +14,13 @@ context('List View Settings', () => {
|
|||
cy.get('button').contains('Menu').click();
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
|
||||
|
||||
cy.get('input[data-fieldname="disable_count"]').check({force: true});
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({force: true});
|
||||
cy.get('button').filter(':visible').contains('Save').click();
|
||||
|
||||
|
||||
cy.reload();
|
||||
|
||||
|
||||
cy.get('.list-count').should('be.empty');
|
||||
cy.get('.list-sidebar .sidebar-stat').should('not.exist');
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ context('Login', () => {
|
|||
|
||||
it('logs in using correct credentials', () => {
|
||||
cy.get('#login_email').type('Administrator');
|
||||
cy.get('#login_password').type('qwe');
|
||||
cy.get('#login_password').type(Cypress.config('adminPassword'));
|
||||
|
||||
cy.get('.btn-login').click();
|
||||
cy.location('pathname').should('eq', '/desk');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
context('Recorder', () => {
|
||||
before(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
});
|
||||
|
||||
it('Navigate to Recorder', () => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
context('Relative Timeframe', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
});
|
||||
before(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
cy.visit('/desk');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.test_utils.create_todo_records");
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
});
|
||||
});
|
||||
it('set relative filter for Previous and check list', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
context('Table MultiSelect', () => {
|
||||
beforeEach(() => {
|
||||
cy.login('Administrator', 'qwe');
|
||||
cy.login();
|
||||
});
|
||||
|
||||
let name = 'table multiselect' + Math.random().toString().slice(2, 8);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ import 'cypress-file-upload';
|
|||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
if (!email) {
|
||||
email = 'Administrator';
|
||||
}
|
||||
if (!password) {
|
||||
password = Cypress.config('adminPassword');
|
||||
}
|
||||
cy.request({
|
||||
url: '/api/method/login',
|
||||
method: 'POST',
|
||||
|
|
@ -35,6 +41,29 @@ Cypress.Commands.add('login', (email, password) => {
|
|||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('call', (method, args) => {
|
||||
return cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
url: `/api/method/${method}`,
|
||||
method: 'POST',
|
||||
body: args,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
}
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
return res.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('create_records', (doc) => {
|
||||
return cy.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc })
|
||||
.then(r => r.message);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fill_field', (fieldname, value, fieldtype='Data') => {
|
||||
let selector = `.form-control[data-fieldname="${fieldname}"]`;
|
||||
|
||||
|
|
@ -72,15 +101,9 @@ Cypress.Commands.add('clear_cache', () => {
|
|||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('dialog', (title, fields) => {
|
||||
cy.window().then(win => {
|
||||
var d = new win.frappe.ui.Dialog({
|
||||
title: title,
|
||||
fields: fields,
|
||||
primary_action: function(){
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
Cypress.Commands.add('dialog', (opts) => {
|
||||
return cy.window().then(win => {
|
||||
var d = new win.frappe.ui.Dialog(opts);
|
||||
d.show();
|
||||
return d;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from faker import Faker
|
|||
from .exceptions import *
|
||||
from .utils.jinja import (get_jenv, get_template, render_template, get_email_from_template, get_jloader)
|
||||
|
||||
# Hamless for Python 3
|
||||
# Harmless for Python 3
|
||||
# For Python 2 set default encoding to utf-8
|
||||
if sys.version[0] == '2':
|
||||
reload(sys)
|
||||
|
|
|
|||
104
frappe/automation/doctype/auto_repeat/auto_repeat.js
Normal file
104
frappe/automation/doctype/auto_repeat/auto_repeat.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) 2018, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.provide("frappe.auto_repeat");
|
||||
|
||||
frappe.ui.form.on('Auto Repeat', {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['reference_doctype'].get_query = function() {
|
||||
return {
|
||||
query: "frappe.automation.doctype.auto_repeat.auto_repeat.get_auto_repeat_doctypes"
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['reference_document'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"auto_repeat": ''
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['print_format'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": frm.doc.reference_doctype
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
// auto repeat message
|
||||
if (frm.is_new()) {
|
||||
let customize_form_link = `<a href="#Form/Customize Form">${__('Customize Form')}</a>`;
|
||||
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
|
||||
}
|
||||
|
||||
// view document button
|
||||
if (!frm.is_dirty()) {
|
||||
let label = __('View {0}', [__(frm.doc.reference_doctype)]);
|
||||
frm.add_custom_button(label, () =>
|
||||
frappe.set_route("List", frm.doc.reference_doctype, { auto_repeat: frm.doc.name })
|
||||
);
|
||||
}
|
||||
|
||||
// auto repeat schedule
|
||||
frappe.auto_repeat.render_schedule(frm);
|
||||
},
|
||||
|
||||
template: function(frm) {
|
||||
if (frm.doc.template) {
|
||||
frappe.model.with_doc("Email Template", frm.doc.template, () => {
|
||||
let email_template = frappe.get_doc("Email Template", frm.doc.template);
|
||||
frm.set_value("subject", email_template.subject);
|
||||
frm.set_value("message", email_template.response);
|
||||
frm.refresh_field("subject");
|
||||
frm.refresh_field("message");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get_contacts: function(frm) {
|
||||
frm.call('fetch_linked_contacts');
|
||||
},
|
||||
|
||||
preview_message: function(frm) {
|
||||
if (frm.doc.message) {
|
||||
frappe.call({
|
||||
method: "frappe.automation.doctype.auto_repeat.auto_repeat.generate_message_preview",
|
||||
args: {
|
||||
reference_dt: frm.doc.reference_doctype,
|
||||
reference_doc: frm.doc.reference_document,
|
||||
subject: frm.doc.subject,
|
||||
message: frm.doc.message
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frappe.msgprint(r.message.message, r.message.subject)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__("Please setup a message first"), __("Message not setup"))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
if (!frm.is_dirty() && frm.doc.status !== 'Disabled') {
|
||||
frappe.call({
|
||||
method: "get_auto_repeat_schedule",
|
||||
doc: frm.doc
|
||||
}).done((r) => {
|
||||
frm.dashboard.wrapper.empty();
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template("auto_repeat_schedule", {
|
||||
schedule_details : r.message || []
|
||||
})
|
||||
);
|
||||
frm.dashboard.show();
|
||||
});
|
||||
} else {
|
||||
frm.dashboard.hide();
|
||||
}
|
||||
};
|
||||
239
frappe/automation/doctype/auto_repeat/auto_repeat.json
Normal file
239
frappe/automation/doctype/auto_repeat/auto_repeat.json
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
{
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "format:AUT-AR-{#####}",
|
||||
"creation": "2018-03-09 11:22:31.192349",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_1",
|
||||
"disabled",
|
||||
"section_break_3",
|
||||
"reference_doctype",
|
||||
"reference_document",
|
||||
"column_break_5",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"section_break_10",
|
||||
"frequency",
|
||||
"repeat_on_day",
|
||||
"repeat_on_last_day",
|
||||
"column_break_12",
|
||||
"next_schedule_date",
|
||||
"notification",
|
||||
"notify_by_email",
|
||||
"recipients",
|
||||
"get_contacts",
|
||||
"template",
|
||||
"subject",
|
||||
"message",
|
||||
"preview_message",
|
||||
"print_format",
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Document Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_document",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Reference Document",
|
||||
"no_copy": 1,
|
||||
"options": "reference_doctype",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "End Date"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "frequency",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Frequency",
|
||||
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: in_list([\"Monthly\", \"Quarterly\", \"Half-yearly\", \"Yearly\"], doc.frequency) && !doc.repeat_on_last_day\n",
|
||||
"fieldname": "repeat_on_day",
|
||||
"fieldtype": "Int",
|
||||
"label": "Repeat on Day"
|
||||
},
|
||||
{
|
||||
"fieldname": "next_schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Next Schedule Date",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "notification",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Notification"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "notify_by_email",
|
||||
"fieldtype": "Check",
|
||||
"label": "Notify by Email"
|
||||
},
|
||||
{
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "recipients",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Recipients"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email && doc.reference_doctype && doc.reference_document",
|
||||
"fieldname": "get_contacts",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Contacts"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Template",
|
||||
"options": "Email Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>New {{ doc.doctype }} #{{ doc.name }}</code></pre></div>",
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subject"
|
||||
},
|
||||
{
|
||||
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text",
|
||||
"label": "Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.notify_by_email && doc.reference_doctype && doc.reference_document",
|
||||
"fieldname": "preview_message",
|
||||
"fieldtype": "Button",
|
||||
"label": "Preview Message"
|
||||
},
|
||||
{
|
||||
"depends_on": "notify_by_email",
|
||||
"fieldname": "print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Print Format",
|
||||
"options": "Print Format"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "\nActive\nDisabled\nCompleted",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.frequency === 'Monthly'",
|
||||
"fieldname": "repeat_on_last_day",
|
||||
"fieldtype": "Check",
|
||||
"label": "Repeat on Last Day of the Month"
|
||||
}
|
||||
],
|
||||
"modified": "2019-07-17 11:30:51.412317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Auto Repeat",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "reference_document",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "reference_document",
|
||||
"track_changes": 1
|
||||
}
|
||||
375
frappe/automation/doctype/auto_repeat/auto_repeat.py
Normal file
375
frappe/automation/doctype/auto_repeat/auto_repeat.py
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
|
||||
|
||||
class AutoRepeat(Document):
|
||||
def validate(self):
|
||||
self.update_status()
|
||||
self.validate_reference_doctype()
|
||||
self.validate_dates()
|
||||
self.validate_email_id()
|
||||
self.set_dates()
|
||||
self.update_auto_repeat_id()
|
||||
self.unlink_if_applicable()
|
||||
|
||||
validate_template(self.subject or "")
|
||||
validate_template(self.message or "")
|
||||
|
||||
def before_insert(self):
|
||||
if not frappe.flags.in_test:
|
||||
start_date = self.start_date
|
||||
today_date = today()
|
||||
if start_date <= today_date:
|
||||
start_date = today_date
|
||||
|
||||
def after_save(self):
|
||||
frappe.get_doc(self.reference_doctype, self.reference_document).notify_update()
|
||||
|
||||
def on_trash(self):
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, {
|
||||
'auto_repeat': self.name
|
||||
}, 'auto_repeat', '')
|
||||
|
||||
def set_dates(self):
|
||||
if self.disabled:
|
||||
self.next_schedule_date = None
|
||||
else:
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day, self.end_date)
|
||||
|
||||
def unlink_if_applicable(self):
|
||||
if self.status == 'Completed' or self.disabled:
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, 'auto_repeat', '')
|
||||
|
||||
def validate_reference_doctype(self):
|
||||
if not frappe.flags.in_test:
|
||||
if not frappe.get_meta(self.reference_doctype).allow_auto_repeat:
|
||||
frappe.throw(_("Enable Allow Auto Repeat for the doctype {0} in Customize Form").format(self.reference_doctype))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.end_date:
|
||||
self.validate_from_to_dates('start_date', 'end_date')
|
||||
|
||||
if self.end_date == self.start_date:
|
||||
frappe.throw(_('{0} should not be same as {1}').format(frappe.bold('End Date'), frappe.bold('Start Date')))
|
||||
|
||||
def validate_email_id(self):
|
||||
if self.notify_by_email:
|
||||
if self.recipients:
|
||||
email_list = split_emails(self.recipients.replace("\n", ""))
|
||||
from frappe.utils import validate_email_address
|
||||
|
||||
for email in email_list:
|
||||
if not validate_email_address(email):
|
||||
frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
|
||||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def update_auto_repeat_id(self):
|
||||
#check if document is already on auto repeat
|
||||
auto_repeat = frappe.db.get_value(self.reference_doctype, self.reference_document, "auto_repeat")
|
||||
if auto_repeat and auto_repeat != self.name:
|
||||
frappe.throw(_("The {0} is already on auto repeat {1}").format(self.reference_document, auto_repeat))
|
||||
else:
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, "auto_repeat", self.name)
|
||||
|
||||
def update_status(self):
|
||||
if self.disabled:
|
||||
self.status = "Disabled"
|
||||
elif self.is_completed():
|
||||
self.status = "Completed"
|
||||
else:
|
||||
self.status = "Active"
|
||||
|
||||
def is_completed(self):
|
||||
return self.end_date and getdate(self.end_date) < getdate(today())
|
||||
|
||||
def get_auto_repeat_schedule(self):
|
||||
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)
|
||||
row = {
|
||||
"reference_document": self.reference_document,
|
||||
"frequency": self.frequency,
|
||||
"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)
|
||||
|
||||
if self.end_date:
|
||||
start_date = get_next_schedule_date(start_date, self.frequency, self.repeat_on_day, self.repeat_on_last_day)
|
||||
while (getdate(start_date) < getdate(end_date)):
|
||||
row = {
|
||||
"reference_document" : self.reference_document,
|
||||
"frequency" : self.frequency,
|
||||
"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)
|
||||
|
||||
|
||||
return schedule_details
|
||||
|
||||
def create_documents(self):
|
||||
try:
|
||||
new_doc = self.make_new_document()
|
||||
if self.notify_by_email and self.recipients:
|
||||
self.send_notification(new_doc)
|
||||
except Exception:
|
||||
error_log = frappe.log_error(frappe.get_traceback(), _("Auto Repeat Document Creation Failure"))
|
||||
|
||||
self.disable_auto_repeat()
|
||||
|
||||
if self.reference_document and not frappe.flags.in_test:
|
||||
self.notify_error_to_user(error_log)
|
||||
|
||||
def make_new_document(self):
|
||||
reference_doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
new_doc = frappe.copy_doc(reference_doc, ignore_no_copy = False)
|
||||
self.update_doc(new_doc, reference_doc)
|
||||
new_doc.insert(ignore_permissions = True)
|
||||
|
||||
return new_doc
|
||||
|
||||
def update_doc(self, new_doc, reference_doc):
|
||||
new_doc.docstatus = 0
|
||||
if new_doc.meta.get_field('set_posting_time'):
|
||||
new_doc.set('set_posting_time', 1)
|
||||
|
||||
if new_doc.meta.get_field('auto_repeat'):
|
||||
new_doc.set('auto_repeat', self.name)
|
||||
|
||||
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time', 'select_print_heading', 'remarks', 'owner']:
|
||||
if new_doc.meta.get_field(fieldname):
|
||||
new_doc.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
for data in new_doc.meta.fields:
|
||||
if data.fieldtype == 'Date' and data.reqd:
|
||||
new_doc.set(data.fieldname, self.next_schedule_date)
|
||||
|
||||
self.set_auto_repeat_period(new_doc)
|
||||
|
||||
auto_repeat_doc = frappe.get_doc('Auto Repeat', self.name)
|
||||
|
||||
#for any action that needs to take place after the recurring document creation
|
||||
#on recurring method of that doctype is triggered
|
||||
new_doc.run_method('on_recurring', reference_doc = reference_doc, auto_repeat_doc = auto_repeat_doc)
|
||||
|
||||
def set_auto_repeat_period(self, new_doc):
|
||||
mcount = month_map.get(self.frequency)
|
||||
if mcount and new_doc.meta.get_field('from_date') and new_doc.meta.get_field('to_date'):
|
||||
last_ref_doc = frappe.db.get_all(doctype = self.reference_doctype,
|
||||
fields = ['name', 'from_date', 'to_date'],
|
||||
filters = [
|
||||
['auto_repeat', '=', self.name],
|
||||
['docstatus', '<', 2],
|
||||
],
|
||||
order_by = 'creation desc',
|
||||
limit = 1)
|
||||
|
||||
if not last_ref_doc:
|
||||
return
|
||||
|
||||
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
|
||||
|
||||
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
|
||||
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
|
||||
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
|
||||
else:
|
||||
to_date = get_next_date(last_ref_doc[0].to_date, mcount)
|
||||
|
||||
new_doc.set('from_date', from_date)
|
||||
new_doc.set('to_date', to_date)
|
||||
|
||||
def send_notification(self, new_doc):
|
||||
"""Notify concerned people about recurring document generation"""
|
||||
subject = self.subject or ''
|
||||
message = self.message or ''
|
||||
|
||||
if not self.subject:
|
||||
subject = _("New {0}: {1}").format(new_doc.doctype, new_doc.name)
|
||||
elif "{" in self.subject:
|
||||
subject = frappe.render_template(self.subject, {'doc': new_doc})
|
||||
|
||||
if not self.message:
|
||||
message = _("Please find attached {0}: {1}").format(new_doc.doctype, new_doc.name)
|
||||
elif "{" in self.message:
|
||||
message = frappe.render_template(self.message, {'doc': new_doc})
|
||||
|
||||
print_format = self.print_format or 'Standard'
|
||||
|
||||
attachments = [frappe.attach_print(new_doc.doctype, new_doc.name,
|
||||
file_name=new_doc.name, print_format=print_format)]
|
||||
|
||||
make(doctype=new_doc.doctype, name=new_doc.name, recipients=self.recipients,
|
||||
subject=subject, content=message, attachments=attachments, send_email=1)
|
||||
|
||||
def fetch_linked_contacts(self):
|
||||
if self.reference_doctype and self.reference_document:
|
||||
res = frappe.db.get_all('Contact',
|
||||
fields=['email_id'],
|
||||
filters=[
|
||||
['Dynamic Link', 'link_doctype', '=', self.reference_doctype],
|
||||
['Dynamic Link', 'link_name', '=', self.reference_document]
|
||||
])
|
||||
|
||||
email_ids = list(set([d.email_id for d in res]))
|
||||
if not email_ids:
|
||||
frappe.msgprint(_('No contacts linked to document'), alert=True)
|
||||
else:
|
||||
self.recipients = ', '.join(email_ids)
|
||||
|
||||
def disable_auto_repeat(self):
|
||||
frappe.db.set_value('Auto Repeat', self.name, 'disabled', 1)
|
||||
|
||||
def notify_error_to_user(self, error_log):
|
||||
recipients = get_system_managers(only_name=True) + self.owner
|
||||
subject = _("Auto Repeat Document Creation Failed")
|
||||
|
||||
form_link = frappe.utils.get_link_to_form(self.reference_doctype, self.reference_document)
|
||||
auto_repeat_failed_for = _('Auto Repeat failed for {0}').format(form_link)
|
||||
|
||||
error_log_link =frappe.utils.get_link_to_form(error_log.reference_doctype, error_log.reference_document)
|
||||
error_log_message = _('Check the Error Log for more information: {0}').format(error_log_link)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=recipients,
|
||||
subject=subject,
|
||||
template="auto_repeat_fail",
|
||||
args={
|
||||
'auto_repeat_failed_for': auto_repeat_failed_for,
|
||||
'error_log_message': error_log_message
|
||||
},
|
||||
header=[subject, 'red']
|
||||
)
|
||||
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day, repeat_on_last_day = False, end_date = None):
|
||||
month_count = month_map.get(frequency)
|
||||
if month_count and repeat_on_last_day:
|
||||
next_date = get_next_date(start_date, month_count, 31)
|
||||
elif month_count and repeat_on_day:
|
||||
next_date = get_next_date(start_date, month_count, repeat_on_day)
|
||||
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)
|
||||
|
||||
return next_date
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
return dt
|
||||
|
||||
#called through hooks
|
||||
def make_auto_repeat_entry():
|
||||
enqueued_method = 'frappe.automation.doctype.auto_repeat.auto_repeat.create_repeated_entries'
|
||||
jobs = get_jobs()
|
||||
|
||||
if not jobs or enqueued_method not in jobs[frappe.local.site]:
|
||||
date = getdate(today())
|
||||
data = get_auto_repeat_entries(date)
|
||||
frappe.enqueue(enqueued_method, data=data)
|
||||
|
||||
def create_repeated_entries(data):
|
||||
for d in data:
|
||||
doc = frappe.get_doc('Auto Repeat', d.name)
|
||||
|
||||
current_date = getdate(today())
|
||||
schedule_date = getdate(doc.next_schedule_date)
|
||||
|
||||
while 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)
|
||||
|
||||
if schedule_date and not doc.disabled:
|
||||
frappe.db.set_value('Auto Repeat', doc.name, 'next_schedule_date', schedule_date)
|
||||
|
||||
def get_auto_repeat_entries(date=None):
|
||||
if not date:
|
||||
date = getdate(today())
|
||||
return frappe.db.get_all('Auto Repeat', filters=[
|
||||
['next_schedule_date', '<=', date],
|
||||
['status', '=', 'Active']
|
||||
])
|
||||
|
||||
#called through hooks
|
||||
def set_auto_repeat_as_completed():
|
||||
auto_repeat = frappe.get_all("Auto Repeat", filters = {'status': ['!=', 'Disabled']})
|
||||
for entry in auto_repeat:
|
||||
doc = frappe.get_doc("Auto Repeat", entry.name)
|
||||
if doc.is_completed():
|
||||
doc.status = 'Completed'
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = today(), end_date = None):
|
||||
doc = frappe.new_doc('Auto Repeat')
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_document = docname
|
||||
doc.frequency = frequency
|
||||
doc.start_date = start_date
|
||||
if end_date:
|
||||
doc.end_date = end_date
|
||||
doc.save()
|
||||
return doc
|
||||
|
||||
#method for reference_doctype filter
|
||||
def get_auto_repeat_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
res = frappe.db.get_all('Property Setter', {
|
||||
'property': 'allow_auto_repeat',
|
||||
'value': '1',
|
||||
}, ['doc_type'])
|
||||
docs = [r.doc_type for r in res]
|
||||
|
||||
res = frappe.db.get_all('DocType', {
|
||||
'allow_auto_repeat': 1,
|
||||
}, ['name'])
|
||||
docs += [r.name for r in res]
|
||||
docs = set(list(docs))
|
||||
|
||||
return [[d] for d in docs]
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_reference(docname, reference):
|
||||
result = ""
|
||||
try:
|
||||
frappe.db.set_value("Auto Repeat", docname, "reference_document", reference)
|
||||
result = "success"
|
||||
except Exception as e:
|
||||
result = "error"
|
||||
raise e
|
||||
return result
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_message_preview(reference_dt, reference_doc, message=None, subject=None):
|
||||
doc = frappe.get_doc(reference_dt, reference_doc)
|
||||
subject_preview = _("Please add a subject to your email")
|
||||
msg_preview = frappe.render_template(message, {'doc': doc})
|
||||
if subject:
|
||||
subject_preview = frappe.render_template(subject, {'doc': doc})
|
||||
|
||||
return {'message': msg_preview, 'subject': subject_preview}
|
||||
11
frappe/automation/doctype/auto_repeat/auto_repeat_list.js
Normal file
11
frappe/automation/doctype/auto_repeat/auto_repeat_list.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
frappe.listview_settings['Auto Repeat'] = {
|
||||
add_fields: ["next_schedule_date"],
|
||||
get_indicator: function(doc) {
|
||||
var colors = {
|
||||
"Active": "green",
|
||||
"Disabled": "red",
|
||||
"Completed": "blue",
|
||||
};
|
||||
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
|
||||
}
|
||||
};
|
||||
|
|
@ -11,10 +11,9 @@
|
|||
{% for(var i=0; i < schedule_details.length; i++) { %}
|
||||
<tr>
|
||||
<td>{{ schedule_details[i].reference_document }}</td>
|
||||
<td> {{ schedule_details[i].frequency }} </td>
|
||||
<td> {{ schedule_details[i].next_scheduled_date }} </td>
|
||||
<td> {{ __(schedule_details[i].frequency) }} </td>
|
||||
<td> {{ frappe.datetime.str_to_user(schedule_details[i].next_scheduled_date) }} </td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -7,20 +7,19 @@ import unittest
|
|||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.desk.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries, disable_auto_repeat
|
||||
from frappe.automation.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries
|
||||
from frappe.utils import today, add_days, getdate, add_months
|
||||
|
||||
|
||||
def add_custom_fields():
|
||||
df = dict(
|
||||
fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', insert_after='sender',
|
||||
options='Auto Repeat')
|
||||
options='Auto Repeat', hidden=1, print_hide=1, read_only=1)
|
||||
create_custom_field('ToDo', df)
|
||||
|
||||
|
||||
class TestAutoRepeat(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not frappe.db.sql("SELECT `name` FROM `tabCustom Field` WHERE `name`='auto_repeat'"):
|
||||
if not frappe.db.sql("SELECT `fieldname` FROM `tabCustom Field` WHERE `fieldname`='auto_repeat' and `dt`=%s", "Todo"):
|
||||
add_custom_fields()
|
||||
|
||||
def test_daily_auto_repeat(self):
|
||||
|
|
@ -29,8 +28,8 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
|
||||
doc = make_auto_repeat(reference_document=todo.name)
|
||||
self.assertEqual(doc.next_schedule_date, today())
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
todo = frappe.get_doc(doc.reference_doctype, doc.reference_document)
|
||||
|
|
@ -51,8 +50,11 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
dict(doctype='ToDo', description='test recurring todo', assigned_by='Administrator')).insert()
|
||||
|
||||
self.monthly_auto_repeat('ToDo', todo.name, start_date, end_date)
|
||||
#test without end_date
|
||||
todo = frappe.get_doc(dict(doctype='ToDo', description='test recurring todo without end_date', assigned_by='Administrator')).insert()
|
||||
self.monthly_auto_repeat('ToDo', todo.name, start_date)
|
||||
|
||||
def monthly_auto_repeat(self, doctype, docname, start_date, end_date):
|
||||
def monthly_auto_repeat(self, doctype, docname, start_date, end_date = None):
|
||||
def get_months(start, end):
|
||||
diff = (12 * end.year + end.month) - (12 * start.year + start.month)
|
||||
return diff + 1
|
||||
|
|
@ -61,10 +63,10 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
reference_doctype=doctype, frequency='Monthly', reference_document=docname, start_date=start_date,
|
||||
end_date=end_date)
|
||||
|
||||
disable_auto_repeat(doc)
|
||||
doc.disable_auto_repeat()
|
||||
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name})
|
||||
self.assertEqual(len(docnames), 1)
|
||||
|
||||
|
|
@ -72,8 +74,8 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
doc.db_set('disabled', 0)
|
||||
|
||||
months = get_months(getdate(start_date), getdate(today()))
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
|
||||
docnames = frappe.get_all(doc.reference_doctype, {'auto_repeat': doc.name})
|
||||
self.assertEqual(len(docnames), months)
|
||||
|
|
@ -84,8 +86,8 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
|
||||
doc = make_auto_repeat(reference_document=todo.name, notify=1, recipients="test@domain.com", subject="New ToDo",
|
||||
message="A new ToDo has just been created for you")
|
||||
for data in get_auto_repeat_entries(today()):
|
||||
create_repeated_entries(data)
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
new_todo = frappe.db.get_value('ToDo',
|
||||
|
|
@ -100,18 +102,14 @@ def make_auto_repeat(**args):
|
|||
doc = frappe.get_doc({
|
||||
'doctype': 'Auto Repeat',
|
||||
'reference_doctype': args.reference_doctype or 'ToDo',
|
||||
'reference_document': args.reference_document or frappe.db.get_value('ToDo', {'docstatus': 1}, 'name'),
|
||||
'reference_document': args.reference_document or frappe.db.get_value('ToDo', 'name'),
|
||||
'frequency': args.frequency or 'Daily',
|
||||
'start_date': args.start_date or add_days(today(), -1),
|
||||
'end_date': args.end_date or add_days(today(), 2),
|
||||
'submit_on_creation': args.submit_on_creation or 0,
|
||||
'end_date': args.end_date or "",
|
||||
'notify_by_email': args.notify or 0,
|
||||
'recipients': args.recipients or "",
|
||||
'subject': args.subject or "",
|
||||
'message': args.message or ""
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
if not args.do_not_submit:
|
||||
doc.submit()
|
||||
|
||||
return doc
|
||||
|
|
@ -62,7 +62,7 @@ def popen(command, *args, **kwargs):
|
|||
|
||||
return_ = proc.wait()
|
||||
|
||||
if raise_err:
|
||||
if return_ and raise_err:
|
||||
raise subprocess.CalledProcessError(return_, command)
|
||||
|
||||
return return_
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=N
|
|||
reinstall=False, db_type=None):
|
||||
"""Install a new Frappe site"""
|
||||
|
||||
if not force and os.path.exists(site):
|
||||
print('Site {0} already exists'.format(site))
|
||||
sys.exit(1)
|
||||
|
||||
if not db_name:
|
||||
db_name = '_' + hashlib.sha1(site.encode()).hexdigest()[:16]
|
||||
|
||||
|
|
@ -276,6 +280,12 @@ def reload_doctype(context, doctype):
|
|||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
@click.command('add-to-hosts')
|
||||
@pass_context
|
||||
def add_to_hosts(context):
|
||||
"Add site to hosts"
|
||||
for site in context.sites:
|
||||
frappe.commands.popen('echo 127.0.0.1\t{0} | sudo tee -a /etc/hosts'.format(site))
|
||||
|
||||
@click.command('use')
|
||||
@click.argument('site')
|
||||
|
|
@ -347,12 +357,13 @@ def uninstall(context, app, dry_run=False, yes=False):
|
|||
@click.option('--root-login', default='root')
|
||||
@click.option('--root-password')
|
||||
@click.option('--archived-sites-path')
|
||||
@click.option('--no-backup', is_flag=True, default=False)
|
||||
@click.option('--force', help='Force drop-site even if an error is encountered', is_flag=True, default=False)
|
||||
def drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False):
|
||||
_drop_site(site, root_login, root_password, archived_sites_path, force)
|
||||
def drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False, no_backup=False):
|
||||
_drop_site(site, root_login, root_password, archived_sites_path, force, no_backup)
|
||||
|
||||
|
||||
def _drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False):
|
||||
def _drop_site(site, root_login='root', root_password=None, archived_sites_path=None, force=False, no_backup=False):
|
||||
"Remove site from database and filesystem"
|
||||
from frappe.database import drop_user_and_database
|
||||
from frappe.utils.backups import scheduled_backup
|
||||
|
|
@ -361,7 +372,8 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path=
|
|||
frappe.connect()
|
||||
|
||||
try:
|
||||
scheduled_backup(ignore_files=False, force=True)
|
||||
if not no_backup:
|
||||
scheduled_backup(ignore_files=False, force=True)
|
||||
except Exception as err:
|
||||
if force:
|
||||
pass
|
||||
|
|
@ -490,10 +502,7 @@ def browse(context, site):
|
|||
site = site.lower()
|
||||
|
||||
if site in frappe.utils.get_sites():
|
||||
webbrowser.open('http://{site}:{port}'.format(
|
||||
site=site,
|
||||
port=frappe.get_conf(site).webserver_port
|
||||
), new=2)
|
||||
webbrowser.open(frappe.utils.get_site_url(site), new=2)
|
||||
else:
|
||||
click.echo("\nSite named \033[1m{}\033[0m doesn't exist\n".format(site))
|
||||
|
||||
|
|
@ -537,4 +546,5 @@ commands = [
|
|||
browse,
|
||||
start_recording,
|
||||
stop_recording,
|
||||
add_to_hosts
|
||||
]
|
||||
|
|
|
|||
|
|
@ -459,26 +459,26 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
|
|||
sys.exit(ret)
|
||||
|
||||
@click.command('run-ui-tests')
|
||||
@click.option('--app', help="App to run tests on, leave blank for all apps")
|
||||
@click.option('--test', help="Path to the specific test you want to run")
|
||||
@click.option('--test-list', help="Path to the txt file with the list of test cases")
|
||||
@click.option('--profile', is_flag=True, default=False)
|
||||
@click.argument('app')
|
||||
@click.option('--headless', is_flag=True, help="Run UI Test in headless mode")
|
||||
@pass_context
|
||||
def run_ui_tests(context, app=None, test=False, test_list=False, profile=False):
|
||||
def run_ui_tests(context, app, headless=False):
|
||||
"Run UI tests"
|
||||
import frappe.test_runner
|
||||
|
||||
site = get_site(context)
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
app_base_path = os.path.abspath(os.path.join(frappe.get_app_path(app), '..'))
|
||||
site_url = frappe.utils.get_site_url(site)
|
||||
admin_password = frappe.get_conf(site).admin_password
|
||||
|
||||
ret = frappe.test_runner.run_ui_tests(app=app, test=test, test_list=test_list, verbose=context.verbose,
|
||||
profile=profile)
|
||||
if len(ret.failures) == 0 and len(ret.errors) == 0:
|
||||
ret = 0
|
||||
# override baseUrl using env variable
|
||||
site_env = 'CYPRESS_baseUrl={}'.format(site_url)
|
||||
password_env = 'CYPRESS_adminPassword={}'.format(admin_password) if admin_password else ''
|
||||
|
||||
if os.environ.get('CI'):
|
||||
sys.exit(ret)
|
||||
# run for headless mode
|
||||
run_or_open = 'run' if headless else 'open'
|
||||
command = '{site_env} {password_env} yarn run cypress {run_or_open}'
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -92,11 +92,6 @@ def get_data():
|
|||
"name": "Google Settings",
|
||||
"description": _("Google API Settings."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Maps Settings",
|
||||
"description": _("Google Maps integration"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "GCalendar Settings",
|
||||
|
|
|
|||
|
|
@ -169,11 +169,27 @@ def get_data():
|
|||
"name": "Workflow Action",
|
||||
"description": _("Actions for workflow (e.g. Approve, Cancel).")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Automation"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Assignment Rule",
|
||||
"description": _("Set up rules for user assignments.")
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Milestone",
|
||||
"description": _("Tracks milestones on the lifecycle of a document if it undergoes multiple stages.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Auto Repeat",
|
||||
"description": _("Automatically generates recurring documents.")
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -161,3 +161,16 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
|||
'link_name': link_name,
|
||||
'link_doctype': link_doctype
|
||||
})
|
||||
|
||||
|
||||
def get_contact_with_phone_number(number):
|
||||
if not number: return
|
||||
|
||||
contacts = frappe.get_all('Contact', or_filters={
|
||||
'phone': ['like', '%{}'.format(number)],
|
||||
'mobile_no': ['like', '%{}'.format(number)]
|
||||
}, limit=1)
|
||||
|
||||
contact = contacts[0].name if contacts else None
|
||||
|
||||
return contact
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ def update_mins_to_first_communication(parent, communication):
|
|||
if frappe.db.get_all('User', filters={'email': communication.sender,
|
||||
'user_type': 'System User', 'enabled': 1}, limit=1):
|
||||
first_responded_on = communication.creation
|
||||
if parent.meta.has_field('first_responded_on'):
|
||||
if parent.meta.has_field('first_responded_on') and communication.sent_or_received == "Sent":
|
||||
parent.db_set('first_responded_on', first_responded_on)
|
||||
parent.db_set('mins_to_first_response', round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,15 @@ frappe.ui.form.on('DocType', {
|
|||
}
|
||||
|
||||
if (!frm.is_new() && !frm.doc.istable) {
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
|
||||
frappe.set_route('List', frm.doc.name, 'List');
|
||||
});
|
||||
if (frm.doc.issingle) {
|
||||
frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => {
|
||||
frappe.set_route('Form', frm.doc.name);
|
||||
});
|
||||
} else {
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
|
||||
frappe.set_route('List', frm.doc.name, 'List');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!frappe.boot.developer_mode && !frm.doc.custom) {
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@
|
|||
"allow_rename",
|
||||
"allow_import",
|
||||
"allow_events_in_timeline",
|
||||
"allow_auto_repeat",
|
||||
"view_settings",
|
||||
"title_field",
|
||||
"search_fields",
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.",
|
||||
"fieldname": "is_submittable",
|
||||
|
|
@ -88,6 +90,7 @@
|
|||
"label": "Is Submittable"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Child Tables are shown as a Grid in other DocTypes",
|
||||
"fieldname": "istable",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -97,6 +100,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "Single Types have only one record no tables associated. Values are stored in tabSingles",
|
||||
"fieldname": "issingle",
|
||||
|
|
@ -135,6 +139,7 @@
|
|||
"label": "Track Changes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"description": "If enabled, the document is marked as seen, the first time a user opens it",
|
||||
"fieldname": "track_seen",
|
||||
|
|
@ -150,11 +155,13 @@
|
|||
"label": "Track Views"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Custom?"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "beta",
|
||||
"fieldtype": "Check",
|
||||
"label": "Beta"
|
||||
|
|
@ -236,6 +243,7 @@
|
|||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hide_toolbar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Sidebar and Menu",
|
||||
|
|
@ -243,6 +251,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Copy",
|
||||
|
|
@ -250,6 +259,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_rename",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Rename",
|
||||
|
|
@ -257,15 +267,23 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_import",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Import (via Data Import Tool)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_events_in_timeline",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow events in timeline"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_auto_repeat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Auto Repeat"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "view_settings",
|
||||
|
|
@ -329,6 +347,13 @@
|
|||
"label": "Color"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_preview_popup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Preview Popup"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_name_in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "Make \"name\" searchable in Global Search"
|
||||
|
|
@ -354,6 +379,7 @@
|
|||
"options": "Domain"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "User Cannot Search",
|
||||
|
|
@ -361,6 +387,7 @@
|
|||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_create",
|
||||
"fieldtype": "Check",
|
||||
"label": "User Cannot Create",
|
||||
|
|
@ -411,17 +438,11 @@
|
|||
"fieldtype": "Select",
|
||||
"label": "Database Engine",
|
||||
"options": "InnoDB\nMyISAM"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_preview_popup",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Preview Popup"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
"idx": 6,
|
||||
"modified": "2019-05-16 14:58:33.405381",
|
||||
"modified": "2019-07-04 23:23:17.174960",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
@ -454,4 +475,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from frappe.utils import now, cint
|
|||
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.desk.notifications import delete_notification_count_for
|
||||
from frappe.modules import make_boilerplate, get_doc_path
|
||||
from frappe.database.schema import validate_column_name, validate_column_length
|
||||
|
|
@ -47,7 +48,8 @@ class DocType(Document):
|
|||
- Validate series
|
||||
- Check fieldnames (duplication etc)
|
||||
- Clear permission table for child tables
|
||||
- Add `amended_from` and `amended_by` if Amendable"""
|
||||
- Add `amended_from` and `amended_by` if Amendable
|
||||
- Add custom field `auto_repeat` if Repeatable"""
|
||||
|
||||
self.check_developer_mode()
|
||||
|
||||
|
|
@ -76,6 +78,7 @@ class DocType(Document):
|
|||
validate_permissions(self)
|
||||
|
||||
self.make_amendable()
|
||||
self.make_repeatable()
|
||||
self.validate_website()
|
||||
|
||||
if not self.is_new():
|
||||
|
|
@ -384,7 +387,7 @@ class DocType(Document):
|
|||
os.path.join(new_path, fname.replace(frappe.scrub(old), frappe.scrub(new)))])
|
||||
|
||||
self.rename_inside_controller(new, old, new_path)
|
||||
frappe.msgprint('Renamed files and replaced code in controllers, please check!')
|
||||
frappe.msgprint(_('Renamed files and replaced code in controllers, please check!'))
|
||||
|
||||
def rename_inside_controller(self, new, old, new_path):
|
||||
for fname in ('{}.js', '{}.py', '{}_list.js', '{}_calendar.js', 'test_{}.py', 'test_{}.js'):
|
||||
|
|
@ -526,6 +529,14 @@ class DocType(Document):
|
|||
"no_copy": 1
|
||||
})
|
||||
|
||||
def make_repeatable(self):
|
||||
"""If allow_auto_repeat is set, add auto_repeat custom field."""
|
||||
if self.allow_auto_repeat:
|
||||
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat', 'dt': self.name}):
|
||||
insert_after = self.fields[len(self.fields) - 1].fieldname
|
||||
df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1)
|
||||
create_custom_field(self.name, df)
|
||||
|
||||
def get_max_idx(self):
|
||||
"""Returns the highest `idx`"""
|
||||
max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""",
|
||||
|
|
|
|||
|
|
@ -1,95 +1,54 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:domain",
|
||||
"beta": 0,
|
||||
"creation": "2017-05-03 15:07:39.752820",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"autoname": "field:domain",
|
||||
"creation": "2017-05-03 15:07:39.752820",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"domain"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "domain",
|
||||
"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": "Domain",
|
||||
"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": 0,
|
||||
"unique": 0
|
||||
"fieldname": "domain",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Domain",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"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": "2017-09-15 12:26:21.827149",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Domain",
|
||||
"name_case": "",
|
||||
"owner": "makarand@erpnext.com",
|
||||
],
|
||||
"modified": "2019-06-30 13:24:13.732202",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Domain",
|
||||
"owner": "makarand@erpnext.com",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "domain",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "domain",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "domain",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "domain"
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
"""
|
||||
record of files
|
||||
|
||||
|
|
@ -446,7 +447,7 @@ class File(NestedSet):
|
|||
def validate_url(self, df=None):
|
||||
if self.file_url:
|
||||
if not self.file_url.startswith(("http://", "https://", "/files/", "/private/files/")):
|
||||
frappe.throw("URL must start with 'http://' or 'https://'")
|
||||
frappe.throw(_("URL must start with 'http://' or 'https://'"))
|
||||
return
|
||||
|
||||
self.file_url = unquote(self.file_url)
|
||||
|
|
@ -682,7 +683,7 @@ def get_web_image(file_url):
|
|||
frappe.msgprint(_("Unable to read file format for {0}").format(file_url))
|
||||
raise
|
||||
|
||||
image = Image.open(StringIO(r.content))
|
||||
image = Image.open(StringIO(frappe.safe_decode(r.content)))
|
||||
|
||||
try:
|
||||
filename, extn = file_url.rsplit("/", 1)[1].rsplit(".", 1)
|
||||
|
|
|
|||
|
|
@ -1,173 +1,72 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:language_code",
|
||||
"beta": 0,
|
||||
"creation": "2014-08-22 16:12:17.249590",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "language_code",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Language Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "language_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Language Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "flag",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Flag",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "based_on",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Language",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-globe",
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-12-29 14:40:33.210645",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Language",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "language_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "language_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:language_code",
|
||||
"creation": "2014-08-22 16:12:17.249590",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"language_code",
|
||||
"language_name",
|
||||
"flag",
|
||||
"based_on"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "language_code",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Language Code",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "language_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Language Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "flag",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Flag"
|
||||
},
|
||||
{
|
||||
"fieldname": "based_on",
|
||||
"fieldtype": "Link",
|
||||
"label": "Based On",
|
||||
"options": "Language"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-globe",
|
||||
"modified": "2019-07-19 16:32:12.652550",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Language",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Guest",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "language_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "language_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ from frappe.modules.export_file import export_to_files
|
|||
from frappe.modules import make_boilerplate
|
||||
from frappe.core.doctype.page.page import delete_custom_role
|
||||
from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles
|
||||
from frappe.desk.reportview import append_totals_row
|
||||
from six import iteritems
|
||||
|
||||
|
||||
|
|
@ -76,11 +77,6 @@ class Report(Document):
|
|||
if not self.json:
|
||||
self.json = '{}'
|
||||
|
||||
if self.json:
|
||||
data = json.loads(self.json)
|
||||
data["add_total_row"] = self.add_total_row
|
||||
self.json = json.dumps(data)
|
||||
|
||||
def export_doc(self):
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
|
|
@ -178,6 +174,9 @@ class Report(Document):
|
|||
|
||||
out = out + [list(d) for d in result]
|
||||
|
||||
if params.get('add_totals_row'):
|
||||
out = append_totals_row(out)
|
||||
|
||||
if as_dict:
|
||||
data = []
|
||||
for row in out:
|
||||
|
|
|
|||
29
frappe/core/doctype/session_default/session_default.json
Normal file
29
frappe/core/doctype/session_default/session_default.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"creation": "2019-07-17 16:21:33.546379",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"ref_doctype"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "ref_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-07-21 13:22:25.752553",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Session Default",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/session_default/session_default.py
Normal file
10
frappe/core/doctype/session_default/session_default.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 SessionDefault(Document):
|
||||
pass
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// MIT License. See license.txt
|
||||
|
||||
frappe.ui.form.on('Session Default Settings', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('ref_doctype', 'session_defaults', function() {
|
||||
return {
|
||||
filters: {
|
||||
issingle: 0,
|
||||
istable: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"creation": "2019-07-17 16:22:31.300991",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"session_defaults"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "session_defaults",
|
||||
"fieldtype": "Table",
|
||||
"label": "Session Defaults",
|
||||
"options": "Session Default"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"modified": "2019-07-19 16:04:33.971089",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Session Default Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# -*- 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 import _
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SessionDefaultSettings(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_session_default_values():
|
||||
settings = frappe.get_single('Session Default Settings')
|
||||
fields = []
|
||||
for default_values in settings.session_defaults:
|
||||
reference_doctype = frappe.scrub(default_values.ref_doctype)
|
||||
fields.append({
|
||||
'fieldname': reference_doctype,
|
||||
'fieldtype': 'Link',
|
||||
'options': default_values.ref_doctype,
|
||||
'label': _('Default {0}').format(_(default_values.ref_doctype)),
|
||||
'default': frappe.defaults.get_user_default(reference_doctype)
|
||||
})
|
||||
return json.dumps(fields)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_session_default_values(default_values):
|
||||
if not frappe.flags.in_test:
|
||||
default_values = json.loads(default_values)
|
||||
for entry in default_values:
|
||||
try:
|
||||
frappe.defaults.set_user_default(entry, default_values.get(entry))
|
||||
except Exception:
|
||||
return
|
||||
return "success"
|
||||
|
||||
#called on hook 'on_logout' to clear defaults for the session
|
||||
def clear_session_defaults():
|
||||
settings = frappe.get_single('Session Default Settings').session_defaults
|
||||
for entry in settings:
|
||||
frappe.defaults.clear_user_default(frappe.scrub(entry.ref_doctype))
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.core.doctype.session_default_settings.session_default_settings import set_session_default_values, clear_session_defaults
|
||||
|
||||
class TestSessionDefaultSettings(unittest.TestCase):
|
||||
def test_set_session_default_settings(self):
|
||||
frappe.set_user("Administrator")
|
||||
settings = frappe.get_single("Session Default Settings")
|
||||
settings.session_defaults = []
|
||||
settings.append("session_defaults", {
|
||||
"ref_doctype": "Role"
|
||||
})
|
||||
settings.save()
|
||||
|
||||
set_session_default_values({"role": "Website Manager"})
|
||||
|
||||
todo = frappe.get_doc(dict(doctype="ToDo", description="test session defaults set", assigned_by="Administrator")).insert()
|
||||
self.assertEqual(todo.role, "Website Manager")
|
||||
|
||||
def test_clear_session_defaults(self):
|
||||
clear_session_defaults()
|
||||
todo = frappe.get_doc(dict(doctype="ToDo", description="test session defaults cleared", assigned_by="Administrator")).insert()
|
||||
self.assertNotEqual(todo.role, "Website Manager")
|
||||
|
|
@ -15,7 +15,7 @@ frappe.ui.form.on('Success Action', {
|
|||
validate: (frm) => {
|
||||
const checked_actions = frm.action_multicheck.get_checked_options();
|
||||
if (checked_actions.length < 2) {
|
||||
frappe.msgprint('Select atleast 2 actions');
|
||||
frappe.msgprint(__('Select atleast 2 actions'));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -166,7 +166,7 @@ frappe.listview_settings['User Permission'] = {
|
|||
return data;
|
||||
}
|
||||
if(data.apply_to_all_doctypes == 0 && !("applicable_doctypes" in data)) {
|
||||
frappe.throw("Please select applicable Doctypes");
|
||||
frappe.throw(__("Please select applicable Doctypes"));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"doc_type",
|
||||
"properties",
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
"quick_entry",
|
||||
"track_changes",
|
||||
"track_views",
|
||||
"allow_auto_repeat",
|
||||
"image_view",
|
||||
"column_break_5",
|
||||
"title_field",
|
||||
|
|
@ -59,17 +61,20 @@
|
|||
"label": "Max Attachments"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Copy"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "istable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Table",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "istable",
|
||||
"fieldname": "editable_grid",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -82,11 +87,13 @@
|
|||
"label": "Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "track_changes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Track Changes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.image_field",
|
||||
"fieldname": "image_view",
|
||||
"fieldtype": "Check",
|
||||
|
|
@ -150,16 +157,23 @@
|
|||
"options": "Customize Form Field"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "track_views",
|
||||
"fieldtype": "Check",
|
||||
"label": "Track Views"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_auto_repeat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Auto Repeat"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2019-05-13 18:54:40.610862",
|
||||
"modified": "2019-07-01 22:50:50.372465",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form",
|
||||
|
|
@ -177,6 +191,7 @@
|
|||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "doc_type",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ from frappe.utils import cint
|
|||
from frappe.model.document import Document
|
||||
from frappe.model import no_value_fields, core_doctypes_list
|
||||
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.model.docfield import supports_translation
|
||||
|
||||
doctype_properties = {
|
||||
|
|
@ -29,6 +30,7 @@ doctype_properties = {
|
|||
'max_attachments': 'Int',
|
||||
'track_changes': 'Check',
|
||||
'track_views': 'Check',
|
||||
'allow_auto_repeat': 'Check'
|
||||
}
|
||||
|
||||
docfield_properties = {
|
||||
|
|
@ -65,6 +67,7 @@ docfield_properties = {
|
|||
'columns': 'Int',
|
||||
'remember_last_selected_value': 'Check',
|
||||
'allow_bulk_edit': 'Check',
|
||||
'auto_repeat': 'Link'
|
||||
}
|
||||
|
||||
allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
|
||||
|
|
@ -108,6 +111,13 @@ class CustomizeForm(Document):
|
|||
translation = self.get_name_translation()
|
||||
self.label = translation.target_name if translation else ''
|
||||
|
||||
#If allow_auto_repeat is set, add auto_repeat custom field.
|
||||
if self.allow_auto_repeat:
|
||||
if not frappe.db.exists('Custom Field', {'fieldname': 'auto_repeat', 'dt': self.doc_type}):
|
||||
insert_after = self.fields[len(self.fields) - 1].fieldname
|
||||
df = dict(fieldname='auto_repeat', label='Auto Repeat', fieldtype='Link', options='Auto Repeat', insert_after=insert_after, read_only=1, no_copy=1, print_hide=1)
|
||||
create_custom_field(self.doc_type, df)
|
||||
|
||||
# NOTE doc is sent to clientside by run_method
|
||||
|
||||
def get_name_translation(self):
|
||||
|
|
|
|||
|
|
@ -553,6 +553,10 @@ class Database(object):
|
|||
val = val[0][0] if val else None
|
||||
|
||||
df = frappe.get_meta(doctype).get_field(fieldname)
|
||||
|
||||
if not df:
|
||||
frappe.throw(_('Invalid field name: {0}').format(frappe.bold(fieldname)), self.InvalidColumnName)
|
||||
|
||||
if df.fieldtype in frappe.model.numeric_fieldtypes:
|
||||
val = cint(val)
|
||||
|
||||
|
|
@ -924,7 +928,7 @@ class Database(object):
|
|||
conditions=conditions
|
||||
), values)
|
||||
else:
|
||||
frappe.throw('No conditions provided')
|
||||
frappe.throw(_('No conditions provided'))
|
||||
|
||||
def log_touched_tables(self, query, values=None):
|
||||
if values:
|
||||
|
|
|
|||
|
|
@ -48,7 +48,10 @@ class DbManager:
|
|||
if not host:
|
||||
host = self.get_current_host()
|
||||
|
||||
self.db.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
if frappe.conf.get('rds_db', 0) == 1:
|
||||
self.db.sql("GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
else:
|
||||
self.db.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%s';" % (target, user, host))
|
||||
|
||||
def flush_privileges(self):
|
||||
self.db.sql("FLUSH PRIVILEGES")
|
||||
|
|
|
|||
|
|
@ -111,13 +111,10 @@ class PostgresDatabase(Database):
|
|||
|
||||
def format_date(self, date):
|
||||
if not date:
|
||||
return '0001-01-01::DATE'
|
||||
return '0001-01-01'
|
||||
|
||||
if isinstance(date, frappe.string_types):
|
||||
if ':' not in date:
|
||||
date = date + '::DATE'
|
||||
else:
|
||||
date = date.strftime('%Y-%m-%d') + '::DATE'
|
||||
if not isinstance(date, frappe.string_types):
|
||||
date = date.strftime('%Y-%m-%d')
|
||||
|
||||
return date
|
||||
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
// Copyright (c) 2018, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.provide("frappe.auto_repeat");
|
||||
|
||||
frappe.ui.form.on('Auto Repeat', {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['reference_doctype'].get_query = function() {
|
||||
return {
|
||||
query: "frappe.desk.doctype.auto_repeat.auto_repeat.auto_repeat_doctype_query"
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['reference_document'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": 1,
|
||||
"auto_repeat": ''
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['print_format'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"doc_type": frm.doc.reference_doctype
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
if(frm.doc.docstatus == 1) {
|
||||
|
||||
let label = __('View {0}', [__(frm.doc.reference_doctype)]);
|
||||
frm.add_custom_button(__(label),
|
||||
function() {
|
||||
frappe.route_options = {
|
||||
"auto_repeat": frm.doc.name,
|
||||
};
|
||||
frappe.set_route("List", frm.doc.reference_doctype);
|
||||
}
|
||||
);
|
||||
|
||||
if(frm.doc.status != 'Stopped') {
|
||||
frm.add_custom_button(__("Stop"),
|
||||
function() {
|
||||
frm.events.stop_resume_auto_repeat(frm, "Stopped");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(frm.doc.status == 'Stopped') {
|
||||
frm.add_custom_button(__("Restart"),
|
||||
function() {
|
||||
frm.events.stop_resume_auto_repeat(frm, "Resumed");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
frm.toggle_display('auto_repeat_schedule', !in_list(['Stopped', 'Cancelled'], frm.doc.status));
|
||||
if(frm.doc.start_date && !in_list(['Stopped', 'Cancelled'], frm.doc.status)){
|
||||
frappe.auto_repeat.render_schedule(frm);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
stop_resume_auto_repeat: function(frm, status) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.stop_resume_auto_repeat",
|
||||
args: {
|
||||
auto_repeat: frm.doc.name,
|
||||
status: status
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("status", r.message);
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
template: function(frm) {
|
||||
if (frm.doc.template) {
|
||||
frappe.model.with_doc("Email Template", frm.doc.template, () => {
|
||||
let email_template = frappe.get_doc("Email Template", frm.doc.template);
|
||||
frm.set_value("subject", email_template.subject);
|
||||
frm.set_value("message", email_template.response);
|
||||
frm.refresh_field("subject");
|
||||
frm.refresh_field("message");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get_contacts: function(frm) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.get_contacts",
|
||||
args: {
|
||||
reference_doctype: frm.doc.reference_doctype,
|
||||
reference_name: frm.doc.reference_document
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frm.set_value("recipients", r.message.join());
|
||||
frm.refresh_field("recipients");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
preview_message: function(frm) {
|
||||
if (frm.doc.message) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.doctype.auto_repeat.auto_repeat.generate_message_preview",
|
||||
args: {
|
||||
reference_dt: frm.doc.reference_doctype,
|
||||
reference_doc: frm.doc.reference_document,
|
||||
subject: frm.doc.subject,
|
||||
message: frm.doc.message
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
frappe.msgprint(r.message.message, r.message.subject)
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__("Please setup a message first"), __("Message not setup"))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.auto_repeat.render_schedule = function(frm) {
|
||||
frappe.call({
|
||||
method: "get_auto_repeat_schedule",
|
||||
doc: frm.doc
|
||||
}).done((r) => {
|
||||
var wrapper = $(frm.fields_dict["auto_repeat_schedule"].wrapper);
|
||||
wrapper.html(frappe.render_template ("auto_repeat_schedule", {"schedule_details" : r.message || []} ));
|
||||
frm.refresh_fields();
|
||||
});
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,418 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import calendar
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
|
||||
|
||||
class AutoRepeat(Document):
|
||||
def validate(self):
|
||||
self.update_status()
|
||||
self.validate_reference_doctype()
|
||||
self.validate_dates()
|
||||
self.validate_next_schedule_date()
|
||||
self.validate_email_id()
|
||||
self.link_party()
|
||||
|
||||
validate_template(self.subject or "")
|
||||
validate_template(self.message or "")
|
||||
|
||||
def before_submit(self):
|
||||
start_date_copy = self.start_date
|
||||
today_copy = add_days(today(), -1)
|
||||
|
||||
if start_date_copy <= today_copy:
|
||||
start_date_copy = today_copy
|
||||
|
||||
if not self.next_schedule_date:
|
||||
self.next_schedule_date = get_next_schedule_date(
|
||||
start_date_copy, self.frequency, self.repeat_on_day)
|
||||
|
||||
def on_submit(self):
|
||||
self.update_auto_repeat_id()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_status()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.validate_dates()
|
||||
self.set_next_schedule_date()
|
||||
|
||||
def before_cancel(self):
|
||||
self.unlink_auto_repeat_id()
|
||||
self.next_schedule_date = None
|
||||
|
||||
def unlink_auto_repeat_id(self):
|
||||
frappe.db.sql(
|
||||
"update `tab{0}` set auto_repeat = null where auto_repeat=%s".format(self.reference_doctype), self.name)
|
||||
|
||||
def validate_reference_doctype(self):
|
||||
if not frappe.get_meta(self.reference_doctype).has_field('auto_repeat'):
|
||||
frappe.throw(_("Add custom field Auto Repeat in the doctype {0}").format(self.reference_doctype))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
|
||||
frappe.throw(_("End date must be greater than start date"))
|
||||
|
||||
def validate_next_schedule_date(self):
|
||||
if self.repeat_on_day and self.next_schedule_date:
|
||||
next_date = getdate(self.next_schedule_date)
|
||||
if next_date.day != self.repeat_on_day:
|
||||
# if the repeat day is the last day of the month (31)
|
||||
# and the current month does not have as many days,
|
||||
# then the last day of the current month is a valid date
|
||||
lastday = calendar.monthrange(next_date.year, next_date.month)[1]
|
||||
if self.repeat_on_day < lastday:
|
||||
# the specified day of the month is not same as the day specified
|
||||
# or the last day of the month
|
||||
frappe.throw(_("Next Date's day and Repeat on Day of Month must be equal"))
|
||||
|
||||
def validate_email_id(self):
|
||||
if self.notify_by_email:
|
||||
if self.recipients:
|
||||
email_list = split_emails(self.recipients.replace("\n", ""))
|
||||
|
||||
from frappe.utils import validate_email_address
|
||||
for email in email_list:
|
||||
if not validate_email_address(email):
|
||||
frappe.throw(_("{0} is an invalid email address in 'Recipients'").format(email))
|
||||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def set_next_schedule_date(self):
|
||||
if self.repeat_on_day:
|
||||
self.next_schedule_date = get_next_date(self.next_schedule_date, 0, self.repeat_on_day)
|
||||
|
||||
def update_auto_repeat_id(self):
|
||||
frappe.db.set_value(self.reference_doctype, self.reference_document, "auto_repeat", self.name)
|
||||
|
||||
def update_status(self, status=None):
|
||||
self.status = {
|
||||
'0': 'Draft',
|
||||
'1': 'Submitted',
|
||||
'2': 'Cancelled'
|
||||
}[cstr(self.docstatus or 0)]
|
||||
|
||||
if status and status != 'Resumed':
|
||||
self.status = status
|
||||
|
||||
if self.docstatus == 2:
|
||||
self.db_set("status", self.status)
|
||||
|
||||
def get_auto_repeat_schedule(self):
|
||||
schedule_details = []
|
||||
start_date_copy = getdate(self.start_date)
|
||||
end_date_copy = getdate(self.end_date)
|
||||
today_copy = frappe.utils.datetime.date.today()
|
||||
|
||||
if start_date_copy < today_copy:
|
||||
start_date_copy = today_copy
|
||||
|
||||
if not self.end_date:
|
||||
days = 60 if self.frequency in ['Daily', 'Weekly'] else 365
|
||||
end_date_copy = add_days(today_copy, days)
|
||||
|
||||
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
|
||||
while (getdate(start_date_copy) < getdate(end_date_copy)):
|
||||
row = {
|
||||
"reference_document" : self.reference_document,
|
||||
"frequency" : self.frequency,
|
||||
"next_scheduled_date" : start_date_copy
|
||||
}
|
||||
schedule_details.append(row)
|
||||
start_date_copy = get_next_schedule_date(start_date_copy, self.frequency, self.repeat_on_day)
|
||||
|
||||
return schedule_details
|
||||
|
||||
def link_party(self):
|
||||
reference = frappe.get_meta(self.reference_doctype)
|
||||
for field in reference.fields:
|
||||
if field.options in ['Customer', 'Supplier', 'Employee']:
|
||||
self.reference_party_doctype = field.options
|
||||
self.reference_party = frappe.db.get_value(self.reference_doctype, self.reference_document, field.fieldname)
|
||||
break
|
||||
|
||||
def get_next_schedule_date(start_date, frequency, repeat_on_day):
|
||||
mcount = month_map.get(frequency)
|
||||
if mcount:
|
||||
next_date = get_next_date(start_date, mcount, repeat_on_day)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(start_date, days)
|
||||
return next_date
|
||||
|
||||
def make_auto_repeat_entry(date=None):
|
||||
enqueued_method = 'frappe.desk.doctype.auto_repeat.auto_repeat.create_repeated_entries'
|
||||
jobs = get_jobs()
|
||||
|
||||
if not jobs or enqueued_method not in jobs[frappe.local.site]:
|
||||
date = date or today()
|
||||
for data in get_auto_repeat_entries(date):
|
||||
frappe.enqueue(enqueued_method, data=data)
|
||||
|
||||
def create_repeated_entries(data):
|
||||
schedule_date = getdate(data.next_schedule_date)
|
||||
while schedule_date <= getdate(today()) and not frappe.db.get_value('Auto Repeat', data.name, 'disabled'):
|
||||
create_documents(data, schedule_date)
|
||||
schedule_date = get_next_schedule_date(schedule_date, data.frequency, data.repeat_on_day)
|
||||
|
||||
if schedule_date and not frappe.db.get_value('Auto Repeat', data.name, 'disabled'):
|
||||
frappe.db.set_value('Auto Repeat', data.name, 'next_schedule_date', schedule_date)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_auto_repeat_entries(date):
|
||||
return frappe.db.sql(""" select * from `tabAuto Repeat`
|
||||
where docstatus = 1 and next_schedule_date <=%s
|
||||
and reference_document is not null and reference_document != ''
|
||||
and next_schedule_date <= ifnull(end_date, '2199-12-31')
|
||||
and disabled = 0 and status != 'Stopped' """, (date), as_dict=1)
|
||||
|
||||
def create_documents(data, schedule_date):
|
||||
try:
|
||||
doc = make_new_document(data, schedule_date)
|
||||
if data.notify_by_email and data.recipients:
|
||||
print_format = data.print_format or "Standard"
|
||||
send_notification(doc, data, print_format=print_format)
|
||||
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.db.begin()
|
||||
frappe.log_error(frappe.get_traceback(), _("Recurring document creation failure"))
|
||||
disable_auto_repeat(data)
|
||||
frappe.db.commit()
|
||||
if data.reference_document and not frappe.flags.in_test:
|
||||
notify_error_to_user(data)
|
||||
|
||||
def disable_auto_repeat(data):
|
||||
auto_repeat = frappe.get_doc('Auto Repeat', data.name)
|
||||
auto_repeat.db_set('disabled', 1)
|
||||
|
||||
def notify_error_to_user(data):
|
||||
party = ''
|
||||
party_type = ''
|
||||
|
||||
if data.reference_doctype in ['Sales Order', 'Sales Invoice', 'Delivery Note']:
|
||||
party_type = 'customer'
|
||||
elif data.reference_doctype in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||
party_type = 'supplier'
|
||||
|
||||
if party_type:
|
||||
party = frappe.db.get_value(data.reference_doctype, data.reference_document, party_type)
|
||||
|
||||
notify_errors(data.reference_document, data.reference_doctype, party, data.owner, data.name)
|
||||
|
||||
def make_new_document(args, schedule_date):
|
||||
doc = frappe.get_doc(args.reference_doctype, args.reference_document)
|
||||
new_doc = frappe.copy_doc(doc, ignore_no_copy=False)
|
||||
update_doc(new_doc, doc, args, schedule_date)
|
||||
new_doc.insert(ignore_permissions=True)
|
||||
|
||||
if args.submit_on_creation:
|
||||
new_doc.submit()
|
||||
|
||||
return new_doc
|
||||
|
||||
def update_doc(new_document, reference_doc, args, schedule_date):
|
||||
new_document.docstatus = 0
|
||||
if new_document.meta.get_field('set_posting_time'):
|
||||
new_document.set('set_posting_time', 1)
|
||||
|
||||
mcount = month_map.get(args.frequency)
|
||||
|
||||
if new_document.meta.get_field('auto_repeat'):
|
||||
new_document.set('auto_repeat', args.name)
|
||||
|
||||
for fieldname in ['naming_series', 'ignore_pricing_rule', 'posting_time',
|
||||
'select_print_heading', 'remarks', 'owner']:
|
||||
if new_document.meta.get_field(fieldname):
|
||||
new_document.set(fieldname, reference_doc.get(fieldname))
|
||||
|
||||
# copy item fields
|
||||
if new_document.meta.get_field('items'):
|
||||
for i, item in enumerate(new_document.items):
|
||||
for fieldname in ("page_break",):
|
||||
item.set(fieldname, reference_doc.items[i].get(fieldname))
|
||||
|
||||
for data in new_document.meta.fields:
|
||||
if data.fieldtype == 'Date' and data.reqd:
|
||||
new_document.set(data.fieldname, schedule_date)
|
||||
|
||||
set_auto_repeat_period(args, mcount, new_document)
|
||||
|
||||
new_document.run_method("on_recurring", reference_doc=reference_doc, auto_repeat_doc=args)
|
||||
|
||||
def set_auto_repeat_period(args, mcount, new_document):
|
||||
if mcount and new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'):
|
||||
last_ref_doc = frappe.db.sql("""
|
||||
select name, from_date, to_date
|
||||
from `tab{0}`
|
||||
where auto_repeat=%s and docstatus < 2
|
||||
order by creation desc
|
||||
limit 1
|
||||
""".format(args.reference_doctype), args.name, as_dict=1)
|
||||
|
||||
if not last_ref_doc:
|
||||
return
|
||||
|
||||
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
|
||||
|
||||
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
|
||||
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
|
||||
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
|
||||
else:
|
||||
to_date = get_next_date(last_ref_doc[0].to_date, mcount)
|
||||
|
||||
new_document.set('from_date', from_date)
|
||||
new_document.set('to_date', to_date)
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
|
||||
return dt
|
||||
|
||||
def send_notification(new_rv, auto_repeat_doc, print_format='Standard'):
|
||||
"""Notify concerned persons about recurring document generation"""
|
||||
print_format = print_format
|
||||
subject = auto_repeat_doc.subject or ''
|
||||
message = auto_repeat_doc.message or ''
|
||||
|
||||
if not auto_repeat_doc.subject:
|
||||
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
|
||||
elif "{" in auto_repeat_doc.subject:
|
||||
subject = frappe.render_template(auto_repeat_doc.subject, {'doc': new_rv})
|
||||
|
||||
if not auto_repeat_doc.message:
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name)
|
||||
elif "{" in auto_repeat_doc.message:
|
||||
message = frappe.render_template(auto_repeat_doc.message, {'doc': new_rv})
|
||||
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name,
|
||||
file_name=new_rv.name, print_format=print_format)]
|
||||
|
||||
make(doctype=new_rv.doctype, name=new_rv.name, recipients=auto_repeat_doc.recipients,
|
||||
subject=subject, content=message, attachments=attachments, send_email=1)
|
||||
|
||||
def notify_errors(doc, doctype, party, owner, name):
|
||||
recipients = get_system_managers(only_name=True)
|
||||
frappe.sendmail(recipients + [frappe.db.get_value("User", owner, "email")],
|
||||
subject=_("[Urgent] Error while creating recurring %s for %s" % (doctype, doc)),
|
||||
message=frappe.get_template("templates/emails/recurring_document_failed.html").render({
|
||||
"type": _(doctype),
|
||||
"name": doc,
|
||||
"party": party or "",
|
||||
"auto_repeat": name
|
||||
}))
|
||||
try:
|
||||
assign_task_to_owner(name, _("Recurring Documents Failed"), recipients)
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Recurring Documents Failed"))
|
||||
|
||||
def assign_task_to_owner(name, msg, users):
|
||||
for d in users:
|
||||
args = {
|
||||
'doctype': 'Auto Repeat',
|
||||
'assign_to': d,
|
||||
'name': name,
|
||||
'description': msg,
|
||||
'priority': 'High'
|
||||
}
|
||||
assign_to.add(args)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_auto_repeat(doctype, docname):
|
||||
doc = frappe.new_doc('Auto Repeat')
|
||||
|
||||
reference_doc = frappe.get_doc(doctype, docname)
|
||||
doc.reference_doctype = doctype
|
||||
doc.reference_document = docname
|
||||
doc.start_date = reference_doc.get('posting_date') or reference_doc.get('transaction_date')
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
def stop_resume_auto_repeat(auto_repeat, status):
|
||||
doc = frappe.get_doc('Auto Repeat', auto_repeat)
|
||||
frappe.msgprint(_("Auto Repeat has been {0}").format(status))
|
||||
if status == 'Resumed':
|
||||
doc.next_schedule_date = get_next_schedule_date(today(),
|
||||
doc.frequency, doc.repeat_on_day)
|
||||
|
||||
doc.update_status(status)
|
||||
doc.save()
|
||||
|
||||
return doc.status
|
||||
|
||||
def auto_repeat_doctype_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select parent from `tabDocField`
|
||||
where fieldname = 'auto_repeat'
|
||||
and parent like %(txt)s
|
||||
order by
|
||||
if(locate(%(_txt)s, parent), locate(%(_txt)s, parent), 99999),
|
||||
parent
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'key': searchfield,
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_contacts(reference_doctype, reference_name):
|
||||
docfields = frappe.get_meta(reference_doctype).fields
|
||||
|
||||
contact_fields = []
|
||||
for field in docfields:
|
||||
if field.fieldtype == "Link" and field.options == "Contact":
|
||||
contact_fields.append(field.fieldname)
|
||||
|
||||
if contact_fields:
|
||||
contacts = []
|
||||
for contact_field in contact_fields:
|
||||
contacts.append(frappe.db.get_value(reference_doctype, reference_name, contact_field))
|
||||
else:
|
||||
return []
|
||||
|
||||
if contacts:
|
||||
emails = []
|
||||
for contact in contacts:
|
||||
emails.append(frappe.db.get_value("Contact", contact, "email_id"))
|
||||
|
||||
return emails
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_reference(docname, reference):
|
||||
try:
|
||||
frappe.db.set_value("Auto Repeat", docname, "reference_document", reference)
|
||||
return "success"
|
||||
except Exception as e:
|
||||
raise e
|
||||
return "error"
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_message_preview(reference_dt, reference_doc, message=None, subject=None):
|
||||
doc = frappe.get_doc(reference_dt, reference_doc)
|
||||
subject_preview = _("Please add a subject to your email")
|
||||
msg_preview = frappe.render_template(message, {'doc': doc})
|
||||
if subject:
|
||||
subject_preview = frappe.render_template(subject, {'doc': doc})
|
||||
|
||||
return {'message': msg_preview, 'subject': subject_preview}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
frappe.listview_settings['Auto Repeat'] = {
|
||||
add_fields: ["next_schedule_date"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.disabled) {
|
||||
return [__("Disabled"), "red"];
|
||||
} else if(doc.next_schedule_date >= frappe.datetime.get_today() && doc.status != 'Stopped') {
|
||||
return [__("Active"), "green"];
|
||||
} else if(doc.docstatus === 0) {
|
||||
return [__("Draft"), "red", "docstatus,=,0"];
|
||||
} else if(doc.status === 'Stopped') {
|
||||
return [__("Stopped"), "red"];
|
||||
} else {
|
||||
return [__("Expired"), "darkgrey"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
from frappe import _
|
||||
from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
|
||||
from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -44,7 +45,7 @@ def get(chart_name, from_date=None, to_date=None, refresh = None):
|
|||
'''.format(
|
||||
unit_function = get_unit_function(chart.based_on, timegrain),
|
||||
datefield = chart.based_on,
|
||||
aggregate_function = chart.chart_type,
|
||||
aggregate_function = get_aggregate_function(chart.chart_type),
|
||||
value_field = chart.value_based_on or '1',
|
||||
doctype = chart.document_type,
|
||||
conditions = conditions,
|
||||
|
|
@ -66,6 +67,14 @@ def get(chart_name, from_date=None, to_date=None, refresh = None):
|
|||
}]
|
||||
}
|
||||
|
||||
def get_aggregate_function(chart_type):
|
||||
return {
|
||||
"Sum": "SUM",
|
||||
"Count": "COUNT",
|
||||
"Average": "AVG"
|
||||
}[chart_type]
|
||||
|
||||
|
||||
def convert_to_dates(data, timegrain):
|
||||
result = []
|
||||
for d in data:
|
||||
|
|
@ -199,6 +208,6 @@ class DashboardChart(Document):
|
|||
|
||||
def check_required_field(self):
|
||||
if not self.based_on:
|
||||
frappe.throw("Time series based on is required to create a dashboard chart")
|
||||
frappe.throw(_("Time series based on is required to create a dashboard chart"))
|
||||
if not self.document_type:
|
||||
frappe.throw("Document type is required to create a dashboard chart")
|
||||
frappe.throw(_("Document type is required to create a dashboard chart"))
|
||||
|
|
@ -71,7 +71,7 @@ class Event(Document):
|
|||
communication.communication_date = self.starts_on
|
||||
communication.reference_doctype = self.doctype
|
||||
communication.reference_name = self.name
|
||||
communication.communication_medium = communication_mapping[self.event_category] if self.event_category else ""
|
||||
communication.communication_medium = communication_mapping.get(self.event_category) if self.event_category else ""
|
||||
communication.status = "Linked"
|
||||
communication.add_link(participant.reference_doctype, participant.reference_docname)
|
||||
communication.save(ignore_permissions=True)
|
||||
|
|
|
|||
|
|
@ -684,4 +684,4 @@
|
|||
"track_changes": 1,
|
||||
"track_seen": 1,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,4 +252,4 @@ def get_view_logs(doctype, docname):
|
|||
|
||||
if view_logs:
|
||||
logs = view_logs
|
||||
return logs
|
||||
return logs
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ def remove_attach():
|
|||
file_name = frappe.form_dict.get('file_name')
|
||||
frappe.delete_doc('File', fid)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_link():
|
||||
"""validate link when updated by user"""
|
||||
|
|
@ -84,27 +83,23 @@ def update_comment(name, content):
|
|||
doc.save(ignore_permissions=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_next(doctype, value, prev, filters=None, order_by="modified desc"):
|
||||
|
||||
prev = not int(prev)
|
||||
sort_field, sort_order = order_by.split(" ")
|
||||
def get_next(doctype, value, prev, filters, sort_order, sort_field):
|
||||
|
||||
prev = int(prev)
|
||||
if not filters: filters = []
|
||||
if isinstance(filters, string_types):
|
||||
filters = json.loads(filters)
|
||||
|
||||
# condition based on sort order
|
||||
condition = ">" if sort_order.lower()=="desc" else "<"
|
||||
# # condition based on sort order
|
||||
condition = ">" if sort_order.lower() == "asc" else "<"
|
||||
|
||||
# switch the condition
|
||||
if prev:
|
||||
condition = "<" if condition==">" else "<"
|
||||
else:
|
||||
sort_order = "asc" if sort_order.lower()=="desc" else "desc"
|
||||
sort_order = "asc" if sort_order.lower() == "desc" else "desc"
|
||||
condition = "<" if condition == ">" else ">"
|
||||
|
||||
# add condition for next or prev item
|
||||
if not order_by[0] in [f[1] for f in filters]:
|
||||
filters.append([doctype, sort_field, condition, value])
|
||||
# # add condition for next or prev item
|
||||
filters.append([doctype, sort_field, condition, frappe.get_value(doctype, value, sort_field)])
|
||||
|
||||
res = frappe.get_list(doctype,
|
||||
fields = ["name"],
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import json
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_list_settings(doctype):
|
||||
|
|
@ -22,31 +20,37 @@ def set_list_settings(doctype, values):
|
|||
doc = frappe.new_doc("List View Setting")
|
||||
doc.name = doctype
|
||||
frappe.clear_messages()
|
||||
doc.update(json.loads(values))
|
||||
doc.update(frappe.parse_json(values))
|
||||
doc.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_user_assignments_and_count(doctype, current_filters):
|
||||
|
||||
def get_group_by_count(doctype, current_filters, field):
|
||||
current_filters = frappe.parse_json(current_filters)
|
||||
subquery_condition = ''
|
||||
if current_filters:
|
||||
# get the subquery
|
||||
subquery = frappe.get_all(doctype,
|
||||
filters=current_filters, return_query = True)
|
||||
|
||||
subquery = frappe.get_all(doctype, filters=current_filters, return_query = True)
|
||||
if field == 'assigned_to':
|
||||
subquery_condition = ' and `tabToDo`.reference_name in ({subquery})'.format(subquery = subquery)
|
||||
return frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
|
||||
from
|
||||
`tabToDo`, `tabUser`
|
||||
where
|
||||
`tabToDo`.status='Open' and
|
||||
`tabToDo`.owner = `tabUser`.name and
|
||||
`tabUser`.user_type = 'System User'
|
||||
{subquery_condition}
|
||||
group by
|
||||
`tabToDo`.owner
|
||||
order by
|
||||
count desc
|
||||
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)
|
||||
else :
|
||||
return frappe.db.get_list(doctype,
|
||||
filters=current_filters,
|
||||
group_by=field,
|
||||
fields=['count(*) as count', field + ' as name'],
|
||||
order_by='count desc',
|
||||
limit=50,
|
||||
)
|
||||
|
||||
todo_list = frappe.db.sql("""select `tabToDo`.owner as name, count(*) as count
|
||||
from
|
||||
`tabToDo`, `tabUser`
|
||||
where
|
||||
`tabToDo`.status='Open' and
|
||||
`tabToDo`.owner = `tabUser`.name and
|
||||
`tabUser`.user_type = 'System User'
|
||||
{subquery_condition}
|
||||
group by
|
||||
`tabToDo`.owner
|
||||
order by
|
||||
count desc
|
||||
limit 50""".format(subquery_condition = subquery_condition), as_dict=True)
|
||||
|
||||
return todo_list
|
||||
|
|
@ -287,44 +287,141 @@ def get_onboard_items(app, module):
|
|||
|
||||
return onboard_items or fallback_items
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_links_for_module(app, module):
|
||||
return [l.get('label') for l in get_links(app, module)]
|
||||
|
||||
def get_links(app, module):
|
||||
try:
|
||||
sections = get_config(app, frappe.scrub(module))
|
||||
except ImportError:
|
||||
return []
|
||||
|
||||
link_names = []
|
||||
|
||||
links = []
|
||||
for section in sections:
|
||||
for item in section["items"]:
|
||||
link_names.append(item.get("label"))
|
||||
return link_names
|
||||
for item in section['items']:
|
||||
links.append(item)
|
||||
return links
|
||||
|
||||
@frappe.whitelist()
|
||||
def hide_modules_from_desktop(modules):
|
||||
def get_desktop_settings():
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
all_modules = get_modules_from_all_apps_for_user()
|
||||
home_settings = get_home_settings()
|
||||
|
||||
modules_by_name = {}
|
||||
for m in all_modules:
|
||||
modules_by_name[m['module_name']] = m
|
||||
|
||||
module_categories = ['Modules', 'Domains', 'Places', 'Administration']
|
||||
user_modules_by_category = {}
|
||||
|
||||
user_saved_modules_by_category = home_settings.modules_by_category or {}
|
||||
user_saved_links_by_module = home_settings.links_by_module or {}
|
||||
|
||||
def apply_user_saved_links(module):
|
||||
module = frappe._dict(module)
|
||||
all_links = get_links(module.app, module.module_name)
|
||||
module_links_by_label = {}
|
||||
for link in all_links:
|
||||
module_links_by_label[link['label']] = 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]
|
||||
|
||||
return module
|
||||
|
||||
for category in module_categories:
|
||||
if category in user_saved_modules_by_category:
|
||||
user_modules = user_saved_modules_by_category[category]
|
||||
user_modules_by_category[category] = [apply_user_saved_links(modules_by_name[m]) \
|
||||
for m in user_modules]
|
||||
else:
|
||||
user_modules_by_category[category] = [apply_user_saved_links(m) \
|
||||
for m in all_modules if m.get('category') == category]
|
||||
|
||||
# filter out hidden modules
|
||||
if home_settings.hidden_modules:
|
||||
for category in user_modules_by_category:
|
||||
hidden_modules = home_settings.hidden_modules or []
|
||||
modules = user_modules_by_category[category]
|
||||
user_modules_by_category[category] = [module for module in modules if module.module_name not in hidden_modules]
|
||||
|
||||
return user_modules_by_category
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_hidden_modules(category_map):
|
||||
category_map = frappe.parse_json(category_map)
|
||||
home_settings = get_home_settings()
|
||||
|
||||
saved_hidden_modules = home_settings.hidden_modules or []
|
||||
|
||||
for category in category_map:
|
||||
config = frappe._dict(category_map[category])
|
||||
saved_hidden_modules += config.removed or []
|
||||
saved_hidden_modules = [d for d in saved_hidden_modules if d not in (config.added or [])]
|
||||
|
||||
home_settings.hidden_modules = saved_hidden_modules
|
||||
set_home_settings(home_settings)
|
||||
|
||||
return get_desktop_settings()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_modules_order(module_category, modules):
|
||||
modules = frappe.parse_json(modules)
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
home_settings = frappe.parse_json(home_settings or '{}')
|
||||
|
||||
home_settings['hidden_modules'] = modules
|
||||
frappe.db.set_value('User', frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
|
||||
return home_settings
|
||||
home_settings = get_home_settings()
|
||||
|
||||
home_settings.modules_by_category = home_settings.modules_by_category or {}
|
||||
home_settings.modules_by_category[module_category] = modules
|
||||
|
||||
set_home_settings(home_settings)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_links_for_module(module_name, links):
|
||||
home_settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
home_settings = frappe.parse_json(home_settings or '{}')
|
||||
links = frappe.parse_json(links)
|
||||
home_settings = get_home_settings()
|
||||
|
||||
home_settings.setdefault('links', {})
|
||||
home_settings['links'].setdefault(module_name, None)
|
||||
home_settings['links'][module_name] = links
|
||||
home_settings.setdefault('links_by_module', {})
|
||||
home_settings['links_by_module'].setdefault(module_name, None)
|
||||
home_settings['links_by_module'][module_name] = links
|
||||
|
||||
set_home_settings(home_settings)
|
||||
|
||||
return get_desktop_settings()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_options_for_show_hide_cards():
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
all_modules = get_modules_from_all_apps_for_user()
|
||||
home_settings = get_home_settings()
|
||||
|
||||
hidden_modules = home_settings.hidden_modules or []
|
||||
|
||||
options = []
|
||||
for module in all_modules:
|
||||
module = frappe._dict(module)
|
||||
options.append({
|
||||
'category': module.category,
|
||||
'label': module.label,
|
||||
'value': module.module_name,
|
||||
'checked': module.module_name not in hidden_modules
|
||||
})
|
||||
|
||||
return options
|
||||
|
||||
def set_home_settings(home_settings):
|
||||
frappe.cache().hset('home_settings', frappe.session.user, home_settings)
|
||||
frappe.db.set_value('User', frappe.session.user, 'home_settings', json.dumps(home_settings))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_home_settings():
|
||||
def get_from_db():
|
||||
settings = frappe.db.get_value("User", frappe.session.user, 'home_settings')
|
||||
return frappe.parse_json(settings or '{}')
|
||||
|
||||
home_settings = frappe.cache().hget('home_settings', frappe.session.user, get_from_db)
|
||||
return home_settings
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|||
}
|
||||
setTimeout(function() {
|
||||
// Reload
|
||||
window.location.href = '';
|
||||
window.location.href = '/desk';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,7 @@ def export_query():
|
|||
if isinstance(data.get("file_format_type"), string_types):
|
||||
file_format_type = data["file_format_type"]
|
||||
|
||||
include_indentation = data["include_indentation"]
|
||||
if isinstance(data.get("visible_idx"), string_types):
|
||||
visible_idx = json.loads(data.get("visible_idx"))
|
||||
else:
|
||||
|
|
@ -318,7 +319,7 @@ def export_query():
|
|||
columns = get_columns_dict(data.columns)
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx)
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Query Report")
|
||||
|
||||
frappe.response['filename'] = report_name + '.xlsx'
|
||||
|
|
@ -326,7 +327,7 @@ def export_query():
|
|||
frappe.response['type'] = 'binary'
|
||||
|
||||
|
||||
def build_xlsx_data(columns, data, visible_idx):
|
||||
def build_xlsx_data(columns, data, visible_idx,include_indentation):
|
||||
result = [[]]
|
||||
|
||||
# add column headings
|
||||
|
|
@ -344,7 +345,7 @@ def build_xlsx_data(columns, data, visible_idx):
|
|||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
if 'indent' in row and idx == 0:
|
||||
if cint(include_indentation) and 'indent' in row and idx == 0:
|
||||
cell_value = (' ' * cint(row['indent'])) + cell_value
|
||||
row_data.append(cell_value)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -185,6 +185,10 @@ def append_totals_row(data):
|
|||
for i in range(len(row)):
|
||||
if isinstance(row[i], (float, int)):
|
||||
totals[i] = (totals[i] or 0) + row[i]
|
||||
|
||||
if not isinstance(totals[0], (int, float)):
|
||||
totals[0] = 'Total'
|
||||
|
||||
data.append(totals)
|
||||
|
||||
return data
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -18,8 +18,8 @@ app_email = "info@frappe.io"
|
|||
|
||||
docs_app = "frappe_io"
|
||||
|
||||
translation_contribution_url = "https://translate.erpnext.xyz/api/method/translator.api.add_translation"
|
||||
translation_contribution_status = "https://translate.erpnext.xyz/api/method/translator.api.translation_status"
|
||||
translation_contribution_url = "https://translate.erpnext.com/api/method/translator.api.add_translation"
|
||||
translation_contribution_status = "https://translate.erpnext.com/api/method/translator.api.translation_status"
|
||||
|
||||
before_install = "frappe.utils.install.before_install"
|
||||
after_install = "frappe.utils.install.after_install"
|
||||
|
|
@ -78,6 +78,8 @@ on_session_creation = [
|
|||
"frappe.utils.scheduler.reset_enabled_scheduler_events",
|
||||
]
|
||||
|
||||
on_logout = "frappe.core.doctype.session_default_settings.session_default_settings.clear_session_defaults"
|
||||
|
||||
# permissions
|
||||
|
||||
permission_query_conditions = {
|
||||
|
|
@ -173,6 +175,8 @@ scheduler_events = {
|
|||
"frappe.desk.form.document_follow.send_daily_updates",
|
||||
"frappe.social.doctype.energy_point_settings.energy_point_settings.allocate_review_points",
|
||||
"frappe.integrations.doctype.google_contacts.google_contacts.sync",
|
||||
"frappe.automation.doctype.auto_repeat.auto_repeat.make_auto_repeat_entry",
|
||||
"frappe.automation.doctype.auto_repeat.auto_repeat.set_auto_repeat_as_completed"
|
||||
],
|
||||
"daily_long": [
|
||||
"frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backups_daily",
|
||||
|
|
@ -227,6 +231,7 @@ bot_parsers = [
|
|||
setup_wizard_exception = "frappe.desk.page.setup_wizard.setup_wizard.email_setup_wizard_exception"
|
||||
|
||||
before_migrate = ['frappe.patches.v11_0.sync_user_permission_doctype_before_migrate.execute']
|
||||
after_migrate = ['frappe.website.doctype.website_theme.website_theme.generate_theme_files_if_not_exist']
|
||||
|
||||
otp_methods = ['OTP App','Email','SMS']
|
||||
user_privacy_documents = [
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ frappe.ui.form.on('Dropbox Settings', {
|
|||
},
|
||||
|
||||
take_backup: function(frm) {
|
||||
if ((frm.doc.app_access_key && frm.doc.app_secret_key) || (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config)){
|
||||
if (frm.doc.enabled && ((frm.doc.app_access_key && frm.doc.app_secret_key)
|
||||
|| (frm.doc.__onload && frm.doc.__onload.dropbox_setup_via_site_config))) {
|
||||
frm.add_custom_button(__("Take Backup Now"), function(frm){
|
||||
frappe.call({
|
||||
method: "frappe.integrations.doctype.dropbox_settings.dropbox_settings.take_backup",
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ def authorize_access(g_contact, reauthorize=None):
|
|||
|
||||
if "refresh_token" in r:
|
||||
frappe.db.set_value("Google Contacts", google_contact.name, "refresh_token", r.get("refresh_token"))
|
||||
frappe.db.commit()
|
||||
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = "/desk#Form/Google%20Contacts/{}".format(google_contact.name)
|
||||
|
|
@ -93,6 +94,7 @@ def google_callback(client_id=None, redirect_uri=None, code=None):
|
|||
else:
|
||||
google_contact = frappe.cache().hget("google_contacts", "google_contact")
|
||||
frappe.db.set_value("Google Contacts", google_contact, "authorization_code", code)
|
||||
frappe.db.commit()
|
||||
|
||||
authorize_access(google_contact)
|
||||
|
||||
|
|
@ -133,23 +135,24 @@ def sync(g_contact=None):
|
|||
|
||||
for name in connection.get("names"):
|
||||
if name.get("metadata").get("primary"):
|
||||
for email in connection.get("emailAddresses"):
|
||||
if not frappe.db.exists("Contact", {"email_id": email.get("value")}):
|
||||
contacts_updated += 1
|
||||
if connection.get("emailAddresses"):
|
||||
for email in connection.get("emailAddresses"):
|
||||
if not frappe.db.exists("Contact", {"email_id": email.get("value")}):
|
||||
contacts_updated += 1
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"salutation": name.get("honorificPrefix") if name.get("honorificPrefix") else "",
|
||||
"first_name": name.get("givenName") if name.get("givenName") else "",
|
||||
"middle_name": name.get("middleName") if name.get("middleName") else "",
|
||||
"last_name": name.get("familyName") if name.get("familyName") else "",
|
||||
"email_id": email.get("value") if email.get("value") else "",
|
||||
"designation": get_indexed_value(connection.get("organizations"), 0, "title"),
|
||||
"phone": get_indexed_value(connection.get("phoneNumbers"), 0, "value"),
|
||||
"mobile_no": get_indexed_value(connection.get("phoneNumbers"), 1, "value"),
|
||||
"source": "Google Contacts",
|
||||
"google_contacts_description": get_indexed_value(connection.get("organizations"), 0, "name")
|
||||
}).insert(ignore_permissions=True)
|
||||
frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"salutation": name.get("honorificPrefix") if name.get("honorificPrefix") else "",
|
||||
"first_name": name.get("givenName") if name.get("givenName") else "",
|
||||
"middle_name": name.get("middleName") if name.get("middleName") else "",
|
||||
"last_name": name.get("familyName") if name.get("familyName") else "",
|
||||
"email_id": email.get("value") if email.get("value") else "",
|
||||
"designation": get_indexed_value(connection.get("organizations"), 0, "title"),
|
||||
"phone": get_indexed_value(connection.get("phoneNumbers"), 0, "value"),
|
||||
"mobile_no": get_indexed_value(connection.get("phoneNumbers"), 1, "value"),
|
||||
"source": "Google Contacts",
|
||||
"google_contacts_description": get_indexed_value(connection.get("organizations"), 0, "name")
|
||||
}).insert(ignore_permissions=True)
|
||||
if g_contact:
|
||||
return _("{0} Google Contacts synced.").format(contacts_updated) if contacts_updated > 0 else _("No new Google Contacts synced.")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
// Copyright (c) 2017, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Google Maps Settings', {
|
||||
});
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-10-16 17:13:05.684227",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "enabled",
|
||||
"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": "Enabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client_key",
|
||||
"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 Key",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "home_address",
|
||||
"fieldtype": "Link",
|
||||
"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": "Home Address",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Address",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"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": "2018-08-21 14:53:09.170463",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Google Maps Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"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,
|
||||
"track_views": 0
|
||||
}
|
||||
|
|
@ -1,31 +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
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class GoogleMapsSettings(Document):
|
||||
def validate(self):
|
||||
if self.enabled:
|
||||
if not self.client_key:
|
||||
frappe.throw(_("Client key is required"))
|
||||
if not self.home_address:
|
||||
frappe.throw(_("Home Address is required"))
|
||||
|
||||
def get_client(self):
|
||||
if not self.enabled:
|
||||
frappe.throw(_("Google Maps integration is not enabled"))
|
||||
|
||||
import googlemaps
|
||||
|
||||
try:
|
||||
client = googlemaps.Client(key=self.client_key)
|
||||
except Exception as e:
|
||||
frappe.throw(e.message)
|
||||
|
||||
return client
|
||||
|
|
@ -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: Google Maps Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Google Maps
|
||||
() => frappe.tests.make('Google Maps Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
|
||||
class TestGoogleMapsSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
"enable",
|
||||
"google_credentials",
|
||||
"client_id",
|
||||
"client_secret"
|
||||
"client_secret",
|
||||
"api_key"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -32,10 +33,15 @@
|
|||
"fieldtype": "Password",
|
||||
"in_list_view": 1,
|
||||
"label": "Client Secret"
|
||||
},
|
||||
{
|
||||
"fieldname": "api_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "API Key"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"modified": "2019-06-19 15:28:05.957380",
|
||||
"modified": "2019-06-29 13:26:33.201060",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "Google Settings",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"creation": "2019-05-29 01:24:29.585060",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"ldap_group",
|
||||
"erpnext_role"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "ldap_group",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "LDAP Group",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "erpnext_role",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "ERPNext Role",
|
||||
"options": "Role",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-07-15 06:46:38.050408",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "LDAP Group Mapping",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"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 LDAPGroupMapping(Document):
|
||||
pass
|
||||
|
|
@ -1,594 +1,215 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-09-22 04:16:48.829658",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "System",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"enabled",
|
||||
"ldap_server_url",
|
||||
"column_break_4",
|
||||
"base_dn",
|
||||
"password",
|
||||
"section_break_5",
|
||||
"organizational_unit",
|
||||
"default_role",
|
||||
"ldap_search_string",
|
||||
"ldap_email_field",
|
||||
"ldap_username_field",
|
||||
"column_break_11",
|
||||
"ldap_first_name_field",
|
||||
"ldap_middle_name_field",
|
||||
"ldap_last_name_field",
|
||||
"ldap_phone_field",
|
||||
"ldap_mobile_field",
|
||||
"ldap_security",
|
||||
"ssl_tls_mode",
|
||||
"require_trusted_certificate",
|
||||
"column_break_17",
|
||||
"local_private_key_file",
|
||||
"local_server_certificate_file",
|
||||
"local_ca_certs_file",
|
||||
"ldap_group_mappings_section",
|
||||
"ldap_group_field",
|
||||
"ldap_groups"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"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": "Enabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_server_url",
|
||||
"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": "LDAP Server 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": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "organizational_unit",
|
||||
"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": "Organizational Unit",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "base_dn",
|
||||
"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": "Base Distinguished Name (DN)",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "password",
|
||||
"fieldtype": "Password",
|
||||
"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": "Password for Base DN",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "LDAP User Creation and Mapping"
|
||||
},
|
||||
{
|
||||
"fieldname": "organizational_unit",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Organizational Unit for Users",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "default_role",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Role on Creation",
|
||||
"options": "Role",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_search_string",
|
||||
"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": "LDAP Search String",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_first_name_field",
|
||||
"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": "LDAP First Name Field",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_email_field",
|
||||
"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": "LDAP Email Field",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_username_field",
|
||||
"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": "LDAP Username Field",
|
||||
"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": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_first_name_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP First Name Field",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_middle_name_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Middle Name Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_last_name_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Last Name Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_phone_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Phone Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_mobile_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Mobile Field"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ldap_security",
|
||||
"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": "LDAP Security",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "LDAP Security"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Off",
|
||||
"description": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ssl_tls_mode",
|
||||
"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": "SSL/TLS Mode",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Off\nStartTLS",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Off\nStartTLS"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "No",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "require_trusted_certificate",
|
||||
"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": "Require Trusted Certificate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "No\nYes",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "local_private_key_file",
|
||||
"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": "Path to private Key File",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Path to private Key File"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "local_server_certificate_file",
|
||||
"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": "Path to Server Certificate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Path to Server Certificate"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "local_ca_certs_file",
|
||||
"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": "Path to CA Certs File",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Path to CA Certs File"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_group_mappings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "LDAP Group Mappings"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_group_field",
|
||||
"fieldtype": "Data",
|
||||
"label": "LDAP Group Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "ldap_groups",
|
||||
"fieldtype": "Table",
|
||||
"label": "LDAP Group Mappings",
|
||||
"options": "LDAP Group Mapping"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-29 10:56:42.322696",
|
||||
"modified": "2019-07-15 06:48:16.562109",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "LDAP Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -15,155 +15,195 @@ class LDAPSettings(Document):
|
|||
|
||||
if not self.flags.ignore_mandatory:
|
||||
if self.ldap_search_string and self.ldap_search_string.endswith("={0}"):
|
||||
connect_to_ldap(server_url=self.ldap_server_url,
|
||||
base_dn=self.base_dn,
|
||||
password=self.get_password(raise_exception=False),
|
||||
ssl_tls_mode=self.ssl_tls_mode,
|
||||
trusted_cert=self.require_trusted_certificate,
|
||||
private_key_file=self.local_private_key_file,
|
||||
server_cert_file=self.local_server_certificate_file,
|
||||
ca_certs_file=self.local_ca_certs_file
|
||||
)
|
||||
self.connect_to_ldap(base_dn=self.base_dn, password=self.get_password(raise_exception=False))
|
||||
else:
|
||||
frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}"))
|
||||
|
||||
def connect_to_ldap(self, base_dn, password):
|
||||
try:
|
||||
import ldap3
|
||||
import ssl
|
||||
|
||||
def get_ldap_client_settings():
|
||||
#return the settings to be used on the client side.
|
||||
result = {
|
||||
"enabled": False
|
||||
}
|
||||
settings = frappe.get_doc("LDAP Settings")
|
||||
if self.require_trusted_certificate == 'Yes':
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1)
|
||||
else:
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1)
|
||||
|
||||
if settings and settings.enabled:
|
||||
result["enabled"] = True
|
||||
result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login"
|
||||
return result
|
||||
if self.local_private_key_file:
|
||||
tls_configuration.private_key_file = self.local_private_key_file
|
||||
if self.local_server_certificate_file:
|
||||
tls_configuration.certificate_file = self.local_server_certificate_file
|
||||
if self.local_ca_certs_file:
|
||||
tls_configuration.ca_certs_file = self.local_ca_certs_file
|
||||
|
||||
server = ldap3.Server(host=self.ldap_server_url, tls=tls_configuration)
|
||||
bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if self.ssl_tls_mode == "StartTLS" else True
|
||||
|
||||
def connect_to_ldap(server_url,
|
||||
base_dn,
|
||||
password,
|
||||
ssl_tls_mode,
|
||||
trusted_cert,
|
||||
private_key_file,
|
||||
server_cert_file,
|
||||
ca_certs_file):
|
||||
try:
|
||||
import ldap3
|
||||
import ssl
|
||||
conn = ldap3.Connection(
|
||||
server=server,
|
||||
user=base_dn,
|
||||
password=password,
|
||||
auto_bind=bind_type,
|
||||
read_only=True,
|
||||
raise_exceptions=True)
|
||||
|
||||
if trusted_cert == 'Yes':
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED,
|
||||
version=ssl.PROTOCOL_TLSv1)
|
||||
return conn
|
||||
|
||||
except ImportError:
|
||||
msg = _("Please Install the ldap3 library via pip to use ldap functionality.")
|
||||
frappe.throw(msg, title=_("LDAP Not Installed"))
|
||||
except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
|
||||
frappe.throw(_("Invalid username or password"))
|
||||
except Exception as ex:
|
||||
frappe.throw(_(str(ex)))
|
||||
|
||||
@staticmethod
|
||||
def get_ldap_client_settings():
|
||||
# return the settings to be used on the client side.
|
||||
result = {
|
||||
"enabled": False
|
||||
}
|
||||
ldap = frappe.get_doc("LDAP Settings")
|
||||
if ldap.enabled:
|
||||
result["enabled"] = True
|
||||
result["method"] = "frappe.integrations.doctype.ldap_settings.ldap_settings.login"
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def update_user_fields(cls, user, user_data):
|
||||
|
||||
updatable_data = {key: value for key, value in user_data.items() if key != 'email'}
|
||||
|
||||
for key, value in updatable_data.items():
|
||||
setattr(user, key, value)
|
||||
user.save(ignore_permissions=True)
|
||||
|
||||
def sync_roles(self, user, additional_groups=None):
|
||||
|
||||
current_roles = set([d.role for d in user.get("roles")])
|
||||
|
||||
needed_roles = set()
|
||||
needed_roles.add(self.default_role)
|
||||
|
||||
lower_groups = [g.lower() for g in additional_groups or []]
|
||||
|
||||
all_mapped_roles = {r.erpnext_role for r in self.ldap_groups}
|
||||
matched_roles = {r.erpnext_role for r in self.ldap_groups if r.ldap_group.lower() in lower_groups}
|
||||
unmatched_roles = all_mapped_roles.difference(matched_roles)
|
||||
needed_roles.update(matched_roles)
|
||||
roles_to_remove = current_roles.intersection(unmatched_roles)
|
||||
|
||||
if not needed_roles.issubset(current_roles):
|
||||
missing_roles = needed_roles.difference(current_roles)
|
||||
user.add_roles(*missing_roles)
|
||||
|
||||
user.remove_roles(*roles_to_remove)
|
||||
|
||||
def create_or_update_user(self, user_data, groups=None):
|
||||
user = None
|
||||
if frappe.db.exists("User", user_data['email']):
|
||||
user = frappe.get_doc("User", user_data['email'])
|
||||
LDAPSettings.update_user_fields(user=user, user_data=user_data)
|
||||
else:
|
||||
tls_configuration = ldap3.Tls(validate=ssl.CERT_NONE,
|
||||
version=ssl.PROTOCOL_TLSv1)
|
||||
doc = user_data
|
||||
doc.update({
|
||||
"doctype": "User",
|
||||
"send_welcome_email": 0,
|
||||
"language": "",
|
||||
"user_type": "System User",
|
||||
# "roles": [{
|
||||
# "role": self.default_role
|
||||
# }]
|
||||
})
|
||||
user = frappe.get_doc(doc)
|
||||
user.insert(ignore_permissions=True)
|
||||
# always add default role.
|
||||
user.add_roles(self.default_role)
|
||||
if self.ldap_group_field:
|
||||
self.sync_roles(user, groups)
|
||||
return user
|
||||
|
||||
if private_key_file:
|
||||
tls_configuration.private_key_file = private_key_file
|
||||
if server_cert_file:
|
||||
tls_configuration.certificate_file = server_cert_file
|
||||
if ca_certs_file:
|
||||
tls_configuration.ca_certs_file = ca_certs_file
|
||||
def get_ldap_attributes(self):
|
||||
ldap_attributes = [self.ldap_email_field, self.ldap_username_field, self.ldap_first_name_field]
|
||||
|
||||
server = ldap3.Server(host=server_url,
|
||||
tls=tls_configuration)
|
||||
bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if ssl_tls_mode == "StartTLS" else True
|
||||
if self.ldap_group_field:
|
||||
ldap_attributes.append(self.ldap_group_field)
|
||||
|
||||
conn = ldap3.Connection(server=server,
|
||||
user=base_dn,
|
||||
password=password,
|
||||
auto_bind=bind_type,
|
||||
read_only=True,
|
||||
raise_exceptions=True)
|
||||
if self.ldap_middle_name_field:
|
||||
ldap_attributes.append(self.ldap_middle_name_field)
|
||||
|
||||
return conn
|
||||
if self.ldap_last_name_field:
|
||||
ldap_attributes.append(self.ldap_last_name_field)
|
||||
|
||||
except ImportError:
|
||||
msg = _("Please Install the ldap3 library via pip to use ldap functionality.")
|
||||
frappe.throw(msg, title=_("LDAP Not Installed"))
|
||||
except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
|
||||
frappe.throw(_("Invalid Credentials"))
|
||||
except Exception as ex:
|
||||
frappe.throw(_(str(ex)))
|
||||
if self.ldap_phone_field:
|
||||
ldap_attributes.append(self.ldap_phone_field)
|
||||
|
||||
if self.ldap_mobile_field:
|
||||
ldap_attributes.append(self.ldap_mobile_field)
|
||||
|
||||
return ldap_attributes
|
||||
|
||||
def authenticate(self, username, password):
|
||||
|
||||
if not self.enabled:
|
||||
frappe.throw(_("LDAP is not enabled."))
|
||||
|
||||
user_filter = self.ldap_search_string.format(username)
|
||||
ldap_attributes = self.get_ldap_attributes()
|
||||
|
||||
conn = self.connect_to_ldap(self.base_dn, self.get_password(raise_exception=False))
|
||||
|
||||
conn.search(
|
||||
search_base=self.organizational_unit,
|
||||
search_filter="({0})".format(user_filter),
|
||||
attributes=ldap_attributes)
|
||||
|
||||
if len(conn.entries) == 1 and conn.entries[0]:
|
||||
user = conn.entries[0]
|
||||
# only try and connect as the user, once we have their fqdn entry.
|
||||
self.connect_to_ldap(base_dn=user.entry_dn, password=password)
|
||||
|
||||
groups = None
|
||||
if self.ldap_group_field:
|
||||
groups = getattr(user, self.ldap_group_field).values
|
||||
return self.create_or_update_user(self.convert_ldap_entry_to_dict(user), groups=groups)
|
||||
else:
|
||||
frappe.throw(_("Invalid username or password"))
|
||||
|
||||
def convert_ldap_entry_to_dict(self, user_entry):
|
||||
data = {
|
||||
'username': user_entry[self.ldap_username_field].value,
|
||||
'email': user_entry[self.ldap_email_field].value,
|
||||
'first_name': user_entry[self.ldap_first_name_field].value
|
||||
}
|
||||
|
||||
# optional fields
|
||||
|
||||
if self.ldap_middle_name_field:
|
||||
data['middle_name'] = user_entry[self.ldap_middle_name_field].value
|
||||
|
||||
if self.ldap_last_name_field:
|
||||
data['last_name'] = user_entry[self.ldap_last_name_field].value
|
||||
|
||||
if self.ldap_phone_field:
|
||||
data['phone'] = user_entry[self.ldap_phone_field].value
|
||||
|
||||
if self.ldap_mobile_field:
|
||||
data['mobile_no'] = user_entry[self.ldap_mobile_field].value
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def login():
|
||||
# LDAP LOGIN LOGIC
|
||||
args = frappe.form_dict
|
||||
user = authenticate_ldap_user(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
|
||||
ldap = frappe.get_doc("LDAP Settings")
|
||||
|
||||
user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
|
||||
|
||||
frappe.local.login_manager.user = user.name
|
||||
frappe.local.login_manager.post_login()
|
||||
|
||||
# because of a GET request!
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def authenticate_ldap_user(user=None,
|
||||
password=None):
|
||||
|
||||
params = {}
|
||||
settings = frappe.get_doc("LDAP Settings")
|
||||
if settings and settings.enabled:
|
||||
conn = connect_to_ldap(server_url=settings.ldap_server_url,
|
||||
base_dn=settings.base_dn,
|
||||
password=settings.get_password(raise_exception=False),
|
||||
ssl_tls_mode=settings.ssl_tls_mode,
|
||||
trusted_cert=settings.require_trusted_certificate,
|
||||
private_key_file=settings.local_private_key_file,
|
||||
server_cert_file=settings.local_server_certificate_file,
|
||||
ca_certs_file=settings.local_ca_certs_file)
|
||||
|
||||
user_filter = settings.ldap_search_string.format(user)
|
||||
conn.search(search_base=settings.organizational_unit,
|
||||
search_filter="({0})".format(user_filter),
|
||||
attributes=[settings.ldap_email_field,
|
||||
settings.ldap_username_field,
|
||||
settings.ldap_first_name_field])
|
||||
|
||||
if len(conn.entries) > 0 and conn.entries[0]:
|
||||
user = conn.entries[0]
|
||||
params["email"] = str(user[settings.ldap_email_field])
|
||||
params["username"] = str(user[settings.ldap_username_field])
|
||||
params["first_name"] = str(user[settings.ldap_first_name_field])
|
||||
connect_to_ldap(server_url=settings.ldap_server_url,
|
||||
base_dn=user.entry_dn,
|
||||
password=frappe.as_unicode(password),
|
||||
ssl_tls_mode=settings.ssl_tls_mode,
|
||||
trusted_cert=settings.require_trusted_certificate,
|
||||
private_key_file=settings.local_private_key_file,
|
||||
server_cert_file=settings.local_server_certificate_file,
|
||||
ca_certs_file=settings.local_ca_certs_file
|
||||
)
|
||||
return create_user(params)
|
||||
else:
|
||||
frappe.throw(_("Not a valid LDAP user"))
|
||||
else:
|
||||
frappe.throw(_("LDAP is not enabled."))
|
||||
|
||||
|
||||
def create_user(params):
|
||||
if frappe.db.exists("User", params["email"]):
|
||||
user = frappe.get_doc("User", params["email"])
|
||||
user.first_name = params["first_name"]
|
||||
user.username = params["username"]
|
||||
user.save(ignore_permissions=True)
|
||||
return user
|
||||
|
||||
else:
|
||||
params.update({
|
||||
"doctype": "User",
|
||||
"send_welcome_email": 0,
|
||||
"language": "",
|
||||
"user_type": "System User",
|
||||
"roles": [{
|
||||
"role": _("Customer")
|
||||
}]
|
||||
})
|
||||
|
||||
user = frappe.get_doc(params).insert(ignore_permissions=True)
|
||||
|
||||
return user
|
||||
|
|
|
|||
|
|
@ -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 TestLDAPSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -66,15 +66,26 @@ def take_backups_if(freq):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def take_backups_s3():
|
||||
def take_backups_s3(retry_count=0):
|
||||
try:
|
||||
backup_to_s3()
|
||||
send_email(True, "S3 Backup Settings")
|
||||
except JobTimeoutException:
|
||||
if retry_count < 2:
|
||||
args = {
|
||||
"retry_count" :retry_count + 1
|
||||
}
|
||||
enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3",
|
||||
queue='long', timeout=1500, **args)
|
||||
else:
|
||||
notify()
|
||||
except Exception:
|
||||
error_message = frappe.get_traceback()
|
||||
frappe.errprint(error_message)
|
||||
send_email(False, "S3 Backup Settings", error_message)
|
||||
notify()
|
||||
|
||||
def notify():
|
||||
error_message = frappe.get_traceback()
|
||||
frappe.errprint(error_message)
|
||||
send_email(False, "S3 Backup Settings", error_message)
|
||||
|
||||
def send_email(success, service_name, error_status=None):
|
||||
if success:
|
||||
|
|
@ -134,6 +145,7 @@ def upload_file_to_s3(filename, folder, conn, bucket):
|
|||
conn.upload_file(filename, bucket, destpath)
|
||||
|
||||
except Exception as e:
|
||||
frappe.log_error()
|
||||
print("Error uploading: %s" % (e))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -175,10 +175,11 @@ class BaseDocument(object):
|
|||
if not self.doctype:
|
||||
return value
|
||||
if not isinstance(value, BaseDocument):
|
||||
if "doctype" not in value:
|
||||
if "doctype" not in value or value['doctype'] is None:
|
||||
value["doctype"] = self.get_table_field_doctype(key)
|
||||
if not value["doctype"]:
|
||||
raise AttributeError(key)
|
||||
|
||||
value = get_controller(value["doctype"])(value)
|
||||
value.init_valid_columns()
|
||||
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ def get_field_currency(df, doc=None):
|
|||
|
||||
if ":" in cstr(df.get("options")):
|
||||
split_opts = df.get("options").split(":")
|
||||
if len(split_opts)==3:
|
||||
if len(split_opts)==3 and doc.get(split_opts[1]):
|
||||
currency = frappe.get_cached_value(split_opts[0], doc.get(split_opts[1]), split_opts[2])
|
||||
else:
|
||||
currency = doc.get(df.get("options"))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
from __future__ import unicode_literals, print_function
|
||||
from six.moves import range
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr
|
||||
from frappe.build import html_to_js_template
|
||||
import re
|
||||
|
|
@ -62,7 +63,7 @@ def render_include(content):
|
|||
if "{% include" in content:
|
||||
paths = re.findall(r'''{% include\s['"](.*)['"]\s%}''', content)
|
||||
if not paths:
|
||||
frappe.throw('Invalid include path', InvalidIncludePath)
|
||||
frappe.throw(_('Invalid include path'), InvalidIncludePath)
|
||||
|
||||
for path in paths:
|
||||
app, app_path = path.split('/', 1)
|
||||
|
|
|
|||
|
|
@ -236,7 +236,7 @@ frappe.patches.v12_0.set_primary_key_in_series
|
|||
execute:frappe.delete_doc("Page", "modules", ignore_missing=True)
|
||||
frappe.patches.v11_0.set_default_letter_head_source
|
||||
frappe.patches.v12_0.setup_comments_from_communications
|
||||
frappe.patches.v12_0.init_desk_settings #11-03-2019
|
||||
frappe.patches.v12_0.init_desk_settings #16-05-2019
|
||||
frappe.patches.v12_0.replace_null_values_in_tables
|
||||
frappe.patches.v12_0.reset_home_settings
|
||||
frappe.patches.v12_0.update_print_format_type
|
||||
|
|
@ -245,3 +245,4 @@ frappe.patches.v11_0.apply_customization_to_custom_doctype
|
|||
frappe.patches.v12_0.remove_feedback_rating
|
||||
frappe.patches.v12_0.move_form_attachments_to_attachments_folder
|
||||
frappe.patches.v12_0.move_timeline_links_to_dynamic_links
|
||||
frappe.patches.v12_0.delete_feedback_request_if_exists #1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql('''
|
||||
DELETE from `tabDocType`
|
||||
WHERE name = 'Feedback Request'
|
||||
''')
|
||||
|
|
@ -8,4 +8,4 @@ from frappe.desk.moduleview import get_onboard_items
|
|||
def execute():
|
||||
"""Reset the initial customizations for desk, with modules, indices and links."""
|
||||
frappe.reload_doc("core", "doctype", "user")
|
||||
frappe.db.sql("""update `tabUser` set home_settings = %s""", (''), debug=True)
|
||||
frappe.db.sql("""update tabUser set home_settings = ''""")
|
||||
|
|
|
|||
9
frappe/patches/v12_0/website_meta_tag_parent.py
Normal file
9
frappe/patches/v12_0/website_meta_tag_parent.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
# convert all /path to path
|
||||
frappe.db.sql('''
|
||||
UPDATE `tabWebsite Meta Tag`
|
||||
SET parent = SUBSTR(parent, 2)
|
||||
WHERE parent like '/%'
|
||||
''')
|
||||
|
|
@ -25,7 +25,7 @@ def migrate_style_settings():
|
|||
website_theme.no_sidebar = cint(frappe.db.get_single_value("Website Settings", "no_sidebar"))
|
||||
|
||||
website_theme.save()
|
||||
website_theme.use_theme()
|
||||
website_theme.set_as_default()
|
||||
|
||||
def map_color_fields(style_settings, website_theme):
|
||||
color_fields_map = {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class PrintSettings(Document):
|
|||
try:
|
||||
import cups
|
||||
except ImportError:
|
||||
frappe.throw("You need to install pycups to use this feature!")
|
||||
frappe.throw(_("You need to install pycups to use this feature!"))
|
||||
return
|
||||
try:
|
||||
cups.setServer(self.server_ip)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"public/js/frappe/ui/messages.js",
|
||||
"public/js/frappe/translate.js",
|
||||
"public/js/frappe/utils/pretty_date.js",
|
||||
"public/js/lib/microtemplate.js",
|
||||
"public/js/frappe/microtemplate.js",
|
||||
"public/js/frappe/query_string.js",
|
||||
|
||||
"public/js/frappe/ui/dropzone.js",
|
||||
|
|
@ -145,9 +145,7 @@
|
|||
"public/js/frappe/router_history.js",
|
||||
"public/js/frappe/defaults.js",
|
||||
"public/js/frappe/roles_editor.js",
|
||||
"public/js/lib/microtemplate.js",
|
||||
|
||||
"public/js/legacy/handler.js",
|
||||
"public/js/frappe/microtemplate.js",
|
||||
|
||||
"public/js/frappe/ui/page.html",
|
||||
"public/js/frappe/ui/page.js",
|
||||
|
|
@ -276,6 +274,7 @@
|
|||
"public/js/frappe/list/list_sidebar.js",
|
||||
"public/js/frappe/list/list_sidebar.html",
|
||||
"public/js/frappe/list/list_sidebar_stat.html",
|
||||
"public/js/frappe/list/list_sidebar_group_by.js",
|
||||
"public/js/frappe/list/list_view_permission_restrictions.html",
|
||||
|
||||
"public/js/frappe/views/gantt/gantt_view.js",
|
||||
|
|
@ -312,7 +311,7 @@
|
|||
"public/js/frappe/ui/group_by/group_by.js"
|
||||
],
|
||||
"js/web_form.min.js": [
|
||||
"public/js/frappe/misc/datetime.js",
|
||||
"public/js/frappe/utils/datetime.js",
|
||||
"public/js/frappe/web_form/webform_script.js",
|
||||
"public/js/lib/datepicker/datepicker.min.js",
|
||||
"public/js/lib/datepicker/datepicker.en.js"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue