Merge remote-tracking branch 'upstream/develop' into feat-camera-attach
This commit is contained in:
commit
d7b531e63c
382 changed files with 6853 additions and 4196 deletions
|
|
@ -1,2 +0,0 @@
|
|||
exclude_paths:
|
||||
- '**.sql'
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
version = 1
|
||||
|
||||
test_patterns = [
|
||||
"**/test_*.py"
|
||||
]
|
||||
|
||||
exclude_patterns = [
|
||||
"frappe/patches/**",
|
||||
"*.min.js"
|
||||
]
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
||||
|
|
@ -5,5 +5,4 @@ frappe/core/doctype/doctype/boilerplate/*
|
|||
frappe/core/doctype/report/boilerplate/*
|
||||
frappe/public/js/frappe/class.js
|
||||
frappe/templates/includes/*
|
||||
frappe/tests/testcafe/*
|
||||
frappe/www/website_script.js
|
||||
frappe/www/website_script.js
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
"open_url_post": true,
|
||||
"toTitle": true,
|
||||
"lstrip": true,
|
||||
"rstrip": true,
|
||||
"strip": true,
|
||||
"strip_html": true,
|
||||
"replace_all": true,
|
||||
|
|
|
|||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
|
@ -15,7 +15,7 @@ If your issue is not clear or does not meet the guidelines, then it will be clos
|
|||
### General Issue Guidelines
|
||||
|
||||
1. **Search existing Issues:** Before raising a Issue, search if it has been raised before. Maybe add a 👍 or give additional help by creating a mockup if it is not already created.
|
||||
2. **Report each issue separately:** Don't club multiple, unreleated issues in one note.
|
||||
2. **Report each issue separately:** Don't club multiple, unrelated issues in one note.
|
||||
3. **Brief:** Please don't include long explanations. Use screenshots and bullet points instead of descriptive paragraphs.
|
||||
|
||||
### Bug Report Guidelines
|
||||
|
|
|
|||
10
.github/helper/translation.py
vendored
10
.github/helper/translation.py
vendored
|
|
@ -2,7 +2,7 @@ import re
|
|||
import sys
|
||||
|
||||
errors_encounter = 0
|
||||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
||||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
||||
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
|
||||
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
|
||||
f_string_pattern = re.compile(r"_\(f[\"']")
|
||||
|
|
@ -28,7 +28,7 @@ for _file in files_to_scan:
|
|||
has_f_string = f_string_pattern.search(line)
|
||||
if has_f_string:
|
||||
errors_encounter += 1
|
||||
print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}')
|
||||
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
|
@ -36,7 +36,7 @@ for _file in files_to_scan:
|
|||
match = pattern.search(line)
|
||||
error_found = False
|
||||
|
||||
if not match and line.endswith(',\n'):
|
||||
if not match and line.endswith((',\n', '[\n')):
|
||||
# concat remaining text to validate multiline pattern
|
||||
line = "".join(file_lines[line_number - 1:])
|
||||
line = line[start_matches.start() + 1:]
|
||||
|
|
@ -44,11 +44,11 @@ for _file in files_to_scan:
|
|||
|
||||
if not match:
|
||||
error_found = True
|
||||
print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}')
|
||||
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
|
||||
|
||||
if not error_found and not words_pattern.search(line):
|
||||
error_found = True
|
||||
print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}')
|
||||
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
|
||||
|
||||
if error_found:
|
||||
errors_encounter += 1
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -7,7 +7,7 @@ locale
|
|||
*.swp
|
||||
*.egg-info
|
||||
dist/
|
||||
build/
|
||||
# build/
|
||||
frappe/docs/current
|
||||
.vscode
|
||||
node_modules
|
||||
|
|
@ -28,7 +28,7 @@ __pycache__/
|
|||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
# build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
|
|
|
|||
9
.stylelintrc
Normal file
9
.stylelintrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": ["stylelint-config-recommended"],
|
||||
"plugins": ["stylelint-scss"],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true,
|
||||
"no-descending-specificity": null
|
||||
}
|
||||
}
|
||||
21
.travis.yml
21
.travis.yml
|
|
@ -31,19 +31,18 @@ matrix:
|
|||
- name: "Python 3.7 MariaDB"
|
||||
python: 3.7
|
||||
env: DB=mariadb TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
|
||||
- name: "Python 3.7 PostgreSQL"
|
||||
python: 3.7
|
||||
env: DB=postgres TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
|
||||
- name: "Cypress"
|
||||
python: 3.7
|
||||
env: DB=mariadb TYPE=ui
|
||||
before_script:
|
||||
- bench --site test_site execute frappe.utils.install.complete_setup_wizard
|
||||
- bench --site test_site_producer execute frappe.utils.install.complete_setup_wizard
|
||||
script: bench --site test_site run-ui-tests frappe --headless
|
||||
|
||||
before_install:
|
||||
|
|
@ -75,8 +74,10 @@ install:
|
|||
- mkdir ~/frappe-bench/sites/test_site
|
||||
- cp $TRAVIS_BUILD_DIR/.travis/consumer_db/$DB.json ~/frappe-bench/sites/test_site/site_config.json
|
||||
|
||||
- mkdir ~/frappe-bench/sites/test_site_producer
|
||||
- cp $TRAVIS_BUILD_DIR/.travis/producer_db/$DB.json ~/frappe-bench/sites/test_site_producer/site_config.json
|
||||
- if [ $TYPE == "server" ]; then
|
||||
mkdir ~/frappe-bench/sites/test_site_producer;
|
||||
cp $TRAVIS_BUILD_DIR/.travis/producer_db/$DB.json ~/frappe-bench/sites/test_site_producer/site_config.json;
|
||||
fi
|
||||
|
||||
- if [ $DB == "mariadb" ];then
|
||||
mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'";
|
||||
|
|
@ -104,11 +105,11 @@ install:
|
|||
|
||||
- cd ./frappe-bench
|
||||
|
||||
- sed -i 's/watch:/# watch:/g' Procfile
|
||||
- sed -i 's/schedule:/# schedule:/g' Procfile
|
||||
- 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 == "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
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ install:
|
|||
|
||||
- bench start &
|
||||
- bench --site test_site reinstall --yes
|
||||
- bench --site test_site_producer reinstall --yes
|
||||
- if [ $TYPE == "server" ]; then bench --site test_site_producer reinstall --yes; fi
|
||||
- bench build --app frappe
|
||||
|
||||
after_script:
|
||||
|
|
|
|||
10
CODEOWNERS
10
CODEOWNERS
|
|
@ -4,12 +4,12 @@
|
|||
# the repo. Unless a later match takes precedence,
|
||||
|
||||
* @frappe/frappe-review-team
|
||||
website/ @scmmishra
|
||||
web_form/ @scmmishra
|
||||
templates/ @scmmishra
|
||||
www/ @scmmishra
|
||||
website/ @prssanna
|
||||
web_form/ @prssanna
|
||||
templates/ @surajshetty3416
|
||||
www/ @surajshetty3416
|
||||
integrations/ @nextchamp-saqib
|
||||
patches/ @sahil28297
|
||||
patches/ @surajshetty3416
|
||||
dashboard/ @prssanna
|
||||
email/ @saurabh6790
|
||||
event_streaming/ @ruchamahabal
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# stolen from http://cgit.drupalcode.org/octopus/commit/?id=db4f837
|
||||
includedir=`mysql_config --variable=pkgincludedir`
|
||||
thiscwd=`pwd`
|
||||
_THIS_DB_VERSION=`mysql -V 2>&1 | tr -d "\n" | cut -d" " -f6 | awk '{ print $1}' | cut -d"-" -f1 | awk '{ print $1}' | sed "s/[\,']//g"`
|
||||
if [ "$_THIS_DB_VERSION" = "5.5.40" ] && [ ! -e "$includedir-$_THIS_DB_VERSION-fixed.log" ] ; then
|
||||
cd $includedir
|
||||
sudo patch -p1 < $thiscwd/ci/my_config.h.patch &> /dev/null
|
||||
sudo touch $includedir-$_THIS_DB_VERSION-fixed.log
|
||||
fi
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
diff -burp a/my_config.h b/my_config.h
|
||||
--- a/my_config.h 2014-10-09 19:32:46.000000000 -0400
|
||||
+++ b/my_config.h 2014-10-09 19:35:12.000000000 -0400
|
||||
@@ -641,17 +641,4 @@
|
||||
#define SIZEOF_TIME_T 8
|
||||
/* #undef TIME_T_UNSIGNED */
|
||||
|
||||
-/*
|
||||
- stat structure (from <sys/stat.h>) is conditionally defined
|
||||
- to have different layout and size depending on the defined macros.
|
||||
- The correct macro is defined in my_config.h, which means it MUST be
|
||||
- included first (or at least before <features.h> - so, practically,
|
||||
- before including any system headers).
|
||||
-
|
||||
- __GLIBC__ is defined in <features.h>
|
||||
-*/
|
||||
-#ifdef __GLIBC__
|
||||
-#error <my_config.h> MUST be included first!
|
||||
-#endif
|
||||
-
|
||||
#endif
|
||||
|
||||
|
|
@ -3,5 +3,9 @@
|
|||
"projectId": "92odwv",
|
||||
"adminPassword": "admin",
|
||||
"defaultCommandTimeout": 20000,
|
||||
"pageLoadTimeout": 15000
|
||||
"pageLoadTimeout": 15000,
|
||||
"retries": {
|
||||
"runMode": 2,
|
||||
"openMode": 2
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ context('API Resources', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
it('Creates two Comments', () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ context('Awesome Bar', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Barcode', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
function get_dialog_with_barcode() {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Duration', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
function get_dialog_with_duration(hide_days = 0, hide_seconds = 0) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
context('Control Link', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
cy.create_records({
|
||||
doctype: 'ToDo',
|
||||
description: 'this is a test todo for link'
|
||||
|
|
@ -29,8 +29,7 @@ context('Control Link', () => {
|
|||
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.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
|
||||
|
||||
cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
|
||||
cy.wait('@search_link');
|
||||
|
|
@ -50,8 +49,7 @@ context('Control Link', () => {
|
|||
it('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.intercept('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 })
|
||||
|
|
@ -63,9 +61,8 @@ context('Control Link', () => {
|
|||
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.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
|
||||
cy.intercept('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
|
||||
|
||||
cy.get('@todos').then(todos => {
|
||||
cy.get('.frappe-control[data-fieldname=link] input').as('input');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Rating', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
function get_dialog_with_rating() {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name;
|
|||
context('Control Date, Time and DateTime', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.insert_doc('DocType', datetime_doctype, true);
|
||||
});
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ context('Control Date, Time and DateTime', () => {
|
|||
.should('be.visible');
|
||||
cy.get(
|
||||
'.datepickers-container .datepicker.active .datepicker--cell-day.-current-'
|
||||
).click();
|
||||
).click({ force: true });
|
||||
|
||||
cy.window()
|
||||
.its('cur_frm')
|
||||
|
|
|
|||
|
|
@ -1,9 +1,33 @@
|
|||
context('Depends On', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.call('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_child_doctype', {
|
||||
name: 'Child Test Depends On',
|
||||
fields: [
|
||||
{
|
||||
"label": "Child Test Field",
|
||||
"fieldname": "child_test_field",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
},
|
||||
{
|
||||
"label": "Child Dependant Field",
|
||||
"fieldname": "child_dependant_field",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
},
|
||||
{
|
||||
"label": "Child Display Dependant Field",
|
||||
"fieldname": "child_display_dependant_field",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
},
|
||||
]
|
||||
});
|
||||
}).then(frappe => {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
|
||||
name: 'Test Depends On',
|
||||
fields: [
|
||||
{
|
||||
|
|
@ -24,6 +48,13 @@ context('Depends On', () => {
|
|||
"fieldtype": "Data",
|
||||
'depends_on': "eval:doc.test_field=='Value'"
|
||||
},
|
||||
{
|
||||
"label": "Child Test Depends On Field",
|
||||
"fieldname": "child_test_depends_on_field",
|
||||
"fieldtype": "Table",
|
||||
'read_only_depends_on': "eval:doc.test_field=='Some Other Value'",
|
||||
'options': "Child Test Depends On"
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
@ -48,6 +79,30 @@ context('Depends On', () => {
|
|||
cy.get('body').click();
|
||||
cy.get('.control-input [data-fieldname="dependant_field"]').should('not.be.disabled');
|
||||
});
|
||||
it('should set the table and its fields as read only depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.fill_field('dependant_field', 'Some Value');
|
||||
//cy.fill_field('test_field', 'Some Other Value');
|
||||
cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table');
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('@table').find('[data-idx="1"]').as('row1');
|
||||
cy.get('@row1').find('.btn-open-row').click();
|
||||
cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid');
|
||||
//cy.get('@row1-form_in_grid').find('')
|
||||
cy.fill_table_field('child_test_depends_on_field', '1', 'child_test_field', 'Some Value');
|
||||
cy.fill_table_field('child_test_depends_on_field', '1', 'child_dependant_field', 'Some Other Value');
|
||||
|
||||
cy.get('@row1-form_in_grid').find('.grid-collapse-row').click();
|
||||
|
||||
// set the table to read-only
|
||||
cy.fill_field('test_field', 'Some Other Value');
|
||||
|
||||
// grid row form fields should be read-only
|
||||
cy.get('@row1').find('.btn-open-row').click();
|
||||
|
||||
cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_test_field"]').should('be.disabled');
|
||||
cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_dependant_field"]').should('be.disabled');
|
||||
});
|
||||
it('should display the field depending on other fields value', () => {
|
||||
cy.new_form('Test Depends On');
|
||||
cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('FileUploader', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app');
|
||||
});
|
||||
|
||||
function open_upload_dialog() {
|
||||
|
|
@ -14,40 +14,32 @@ context('FileUploader', () => {
|
|||
open_upload_dialog();
|
||||
cy.get_open_dialog().should('contain', 'Drag and drop files');
|
||||
cy.hide_dialog();
|
||||
cy.get('body').click();
|
||||
});
|
||||
|
||||
it('should accept dropped files', () => {
|
||||
open_upload_dialog();
|
||||
|
||||
cy.fixture('example.json').then(fileContent => {
|
||||
cy.get_open_dialog().find('.file-upload-area').upload({
|
||||
fileContent,
|
||||
fileName: 'example.json',
|
||||
mimeType: 'application/json'
|
||||
}, {
|
||||
subjectType: 'drag-n-drop',
|
||||
force: true
|
||||
});
|
||||
cy.get_open_dialog().find('.file-name').should('contain', 'example.json');
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.get_open_dialog().find('.btn-modal-primary').click();
|
||||
cy.wait('@upload_file').its('status').should('be', 200);
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
cy.get_open_dialog().find('.file-upload-area').attachFile('example.json', {
|
||||
subjectType: 'drag-n-drop',
|
||||
});
|
||||
|
||||
cy.get_open_dialog().find('.file-name').should('contain', 'example.json');
|
||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.get_open_dialog().find('.btn-modal-primary').click();
|
||||
cy.wait('@upload_file').its('response.statusCode').should('eq', 200);
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
it('should accept uploaded files', () => {
|
||||
open_upload_dialog();
|
||||
|
||||
cy.get_open_dialog().find('.btn-file-upload div:contains("Library")').click();
|
||||
cy.get('.file-filter').type('example.json');
|
||||
cy.get_open_dialog().find('.tree-label:contains("example.json")').first().click();
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
cy.wait('@upload_file').its('response.body.message')
|
||||
.should('have.property', 'file_url', '/private/files/example.json');
|
||||
.should('have.property', 'file_name', 'example.json');
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
|
|
@ -56,8 +48,7 @@ context('FileUploader', () => {
|
|||
|
||||
cy.get_open_dialog().find('.btn-file-upload div:contains("Link")').click();
|
||||
cy.get_open_dialog().find('.file-web-link input').type('https://github.com', { delay: 100, force: true });
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.get_open_dialog().find('.btn-primary').click();
|
||||
cy.wait('@upload_file').its('response.body.message')
|
||||
.should('have.property', 'file_url', 'https://github.com');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
|
||||
});
|
||||
|
|
@ -11,13 +11,12 @@ context('Form', () => {
|
|||
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
|
||||
cy.wait(300);
|
||||
cy.get('.page-title').should('contain', 'Not Saved');
|
||||
cy.server();
|
||||
cy.route({
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.desk.form.save.savedocs'
|
||||
}).as('form_save');
|
||||
cy.get('.primary-action').click();
|
||||
cy.wait('@form_save').its('status').should('eq', 200);
|
||||
cy.wait('@form_save').its('response.statusCode').should('eq', 200);
|
||||
cy.visit('/app/todo');
|
||||
cy.get('.title-text').should('be.visible').and('contain', 'To Do');
|
||||
cy.get('.list-row').should('contain', 'this is a test todo');
|
||||
|
|
@ -30,21 +29,20 @@ context('Form', () => {
|
|||
cy.visit('/app/contact/Test Form Contact 3');
|
||||
cy.get('.prev-doc').should('be.visible').click();
|
||||
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
|
||||
cy.get('.btn-modal-close:visible').click();
|
||||
cy.hide_dialog();
|
||||
cy.get('.next-doc').click();
|
||||
cy.wait(200);
|
||||
cy.hide_dialog();
|
||||
cy.contains('Test Form Contact 2').should('not.exist');
|
||||
cy.get('.title-text').should('contain', 'Test Form Contact 1');
|
||||
cy.get('.title-text').should('contain', 'Test Form Contact 3');
|
||||
// clear filters
|
||||
cy.window().its('frappe').then((frappe) => {
|
||||
let list_view = frappe.get_list_view('Contact');
|
||||
list_view.filter_area.filter_list.clear_filters();
|
||||
});
|
||||
cy.visit('/app/contact');
|
||||
cy.clear_filters();
|
||||
});
|
||||
it('validates behaviour of Data options validations in child table', () => {
|
||||
// test email validations for set_invalid controller
|
||||
let website_input = 'website.in';
|
||||
let expectBackgroundColor = 'rgb(255, 220, 220)';
|
||||
let expectBackgroundColor = 'rgb(255, 245, 245)';
|
||||
|
||||
cy.visit('/app/contact/new');
|
||||
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
context('Grid Pagination', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('List View', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
|
||||
});
|
||||
|
|
@ -11,20 +11,21 @@ context('List View', () => {
|
|||
cy.go_to_list('ToDo');
|
||||
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true });
|
||||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
|
||||
cy.get('.dropdown-menu li:visible').should('have.length', 8).each((el, index) => {
|
||||
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 8).each((el, index) => {
|
||||
cy.wrap(el).contains(actions[index]);
|
||||
}).then((elements) => {
|
||||
cy.server();
|
||||
cy.route({
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.model.workflow.bulk_workflow_approval'
|
||||
}).as('bulk-approval');
|
||||
cy.route({
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: 'api/method/frappe.desk.reportview.get'
|
||||
}).as('real-time-update');
|
||||
cy.wrap(elements).contains('Approve').click();
|
||||
cy.wait(['@bulk-approval', '@real-time-update']);
|
||||
cy.hide_dialog();
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row-container:visible').should('contain', 'Approved');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('List View Settings', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
it('Default settings', () => {
|
||||
cy.visit('/app/List/DocType/List');
|
||||
|
|
@ -14,8 +14,8 @@ context('List View Settings', () => {
|
|||
cy.wait(300);
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('.menu-btn-group button').click();
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'DocType Settings');
|
||||
|
||||
cy.get('input[data-fieldname="disable_count"]').check({ force: true });
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({ force: true });
|
||||
|
|
@ -27,8 +27,8 @@ context('List View Settings', () => {
|
|||
cy.get('.list-sidebar .list-tags').should('not.exist');
|
||||
|
||||
cy.get('.menu-btn-group button').click({ force: true });
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'DocType Settings');
|
||||
cy.get('input[data-fieldname="disable_count"]').uncheck({ force: true });
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({ force: true });
|
||||
cy.get('button').filter(':visible').contains('Save').click();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ context('Login', () => {
|
|||
beforeEach(() => {
|
||||
cy.request('/api/method/logout');
|
||||
cy.visit('/login');
|
||||
cy.location().should('be', '/login');
|
||||
cy.location('pathname').should('eq', '/login');
|
||||
});
|
||||
|
||||
it('greets with login screen', () => {
|
||||
|
|
@ -35,7 +35,7 @@ context('Login', () => {
|
|||
cy.get('#login_password').type(Cypress.config('adminPassword'));
|
||||
|
||||
cy.get('.btn-login:visible').click();
|
||||
cy.location('pathname').should('eq', '/app/space/Home');
|
||||
cy.location('pathname').should('eq', '/app');
|
||||
cy.window().its('frappe.session.user').should('eq', 'Administrator');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Query Report', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
it('add custom column in report', () => {
|
||||
|
|
|
|||
|
|
@ -4,73 +4,69 @@ context('Recorder', () => {
|
|||
});
|
||||
|
||||
it('Navigate to Recorder', () => {
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app');
|
||||
cy.awesomebar('recorder');
|
||||
cy.get('h1').should('contain', 'Recorder');
|
||||
cy.location('pathname').should('eq', '#recorder');
|
||||
cy.get('h3').should('contain', 'Recorder');
|
||||
cy.url().should('include', '/recorder/detail');
|
||||
});
|
||||
|
||||
// it('Recorder Empty State', () => {
|
||||
// cy.visit('/app/recorder');
|
||||
// cy.get('.title-text').should('contain', 'Recorder');
|
||||
it('Recorder Empty State', () => {
|
||||
cy.visit('/app/recorder');
|
||||
cy.get('.title-text').should('contain', 'Recorder');
|
||||
|
||||
// cy.get('.indicator').should('contain', 'Inactive').should('have.class', 'red');
|
||||
cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red');
|
||||
|
||||
// cy.get('.primary-action').should('contain', 'Start');
|
||||
// cy.get('.btn-secondary').should('contain', 'Clear');
|
||||
cy.get('.primary-action').should('contain', 'Start');
|
||||
cy.get('.btn-secondary').should('contain', 'Clear');
|
||||
|
||||
// cy.get('.msg-box').should('contain', 'Inactive');
|
||||
// cy.get('.msg-box .btn-primary').should('contain', 'Start Recording');
|
||||
// });
|
||||
cy.get('.msg-box').should('contain', 'Inactive');
|
||||
cy.get('.msg-box .btn-primary').should('contain', 'Start Recording');
|
||||
});
|
||||
|
||||
// it('Recorder Start', () => {
|
||||
// cy.visit('/app/recorder');
|
||||
// cy.get('.primary-action').should('contain', 'Start').click();
|
||||
// cy.get('.indicator').should('contain', 'Active').should('have.class', 'green');
|
||||
it('Recorder Start', () => {
|
||||
cy.visit('/app/recorder');
|
||||
cy.get('.primary-action').should('contain', 'Start').click();
|
||||
cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green');
|
||||
|
||||
// cy.get('.msg-box').should('contain', 'No Requests');
|
||||
cy.get('.msg-box').should('contain', 'No Requests');
|
||||
|
||||
// cy.server();
|
||||
// cy.visit('/app/List/DocType/List');
|
||||
// cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.wait('@list_refresh');
|
||||
cy.visit('/app/List/DocType/List');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.wait('@list_refresh');
|
||||
|
||||
// cy.get('.title-text').should('contain', 'DocType');
|
||||
// cy.get('.list-count').should('contain', '20 of ');
|
||||
cy.get('.title-text').should('contain', 'DocType');
|
||||
cy.get('.list-count').should('contain', '20 of ');
|
||||
|
||||
// cy.visit('/app/recorder');
|
||||
// cy.get('.title-text').should('contain', 'Recorder');
|
||||
// cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get');
|
||||
cy.visit('/app/recorder');
|
||||
cy.get('.title-text').should('contain', 'Recorder');
|
||||
cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get');
|
||||
|
||||
// cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
|
||||
// cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
|
||||
// cy.get('.msg-box').should('contain', 'Inactive');
|
||||
// });
|
||||
cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
|
||||
cy.wait(500);
|
||||
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
|
||||
cy.get('.msg-box').should('contain', 'Inactive');
|
||||
});
|
||||
|
||||
// it('Recorder View Request', () => {
|
||||
// cy.visit('/app/recorder');
|
||||
// cy.get('.primary-action').should('contain', 'Start').click();
|
||||
it('Recorder View Request', () => {
|
||||
cy.visit('/app/recorder');
|
||||
cy.get('.primary-action').should('contain', 'Start').click();
|
||||
|
||||
// cy.server();
|
||||
// cy.visit('/app/List/DocType/List');
|
||||
// cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.wait('@list_refresh');
|
||||
cy.visit('/app/List/DocType/List');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.wait('@list_refresh');
|
||||
|
||||
// cy.get('.title-text').should('contain', 'DocType');
|
||||
// cy.get('.list-count').should('contain', '20 of ');
|
||||
cy.get('.title-text').should('contain', 'DocType');
|
||||
cy.get('.list-count').should('contain', '20 of ');
|
||||
|
||||
// temporarily commenting out theses tests as they seem to be
|
||||
// randomly failing maybe due a backround event
|
||||
cy.visit('/app/recorder');
|
||||
|
||||
// cy.visit('/app/recorder');
|
||||
cy.get('.list-row-container span').contains('/api/method/frappe').click();
|
||||
|
||||
// cy.get('.list-row-container span').contains('/api/method/frappe').click();
|
||||
cy.url().should('include', '/recorder/request');
|
||||
cy.get('form').should('contain', '/api/method/frappe');
|
||||
|
||||
// cy.location('hash').should('contain', '#recorder/request/');
|
||||
// cy.get('form').should('contain', '/api/method/frappe');
|
||||
|
||||
// cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
|
||||
// cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
|
||||
// cy.location('hash').should('eq', '#recorder');
|
||||
// });
|
||||
cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
|
||||
cy.wait(200);
|
||||
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
|
||||
});
|
||||
});
|
||||
|
|
@ -4,50 +4,43 @@ context('Relative Timeframe', () => {
|
|||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
cy.window().its('frappe').then(frappe => {
|
||||
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
|
||||
});
|
||||
});
|
||||
it('sets relative timespan filter for last week and filters list', () => {
|
||||
cy.visit('/app/List/ToDo/List');
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
cy.add_filter();
|
||||
// cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
cy.get('select.condition.form-control').select("Timespan");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("last week");
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.get('.filter-box .btn:contains("Apply")').click();
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
cy.wait('@list_refresh');
|
||||
cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
cy.get('.list-row-container').should('contain', 'this is second todo');
|
||||
cy.route('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
it('sets relative timespan filter for next week and filters list', () => {
|
||||
cy.visit('/app/List/ToDo/List');
|
||||
cy.clear_filters();
|
||||
cy.get('.list-row:contains("this is fourth todo")').should('exist');
|
||||
// cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
|
||||
cy.add_filter();
|
||||
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
|
||||
cy.get('select.condition.form-control').select("Timespan");
|
||||
cy.get('.filter-field select.input-with-feedback.form-control').select("next week");
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
// cy.get('.filter-box .btn:contains("Apply")').click();
|
||||
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
cy.wait('@list_refresh');
|
||||
// cy.get('.list-row-container').its('length').should('eq', 1);
|
||||
// cy.get('.list-row').should('contain', 'this is first todo');
|
||||
cy.route('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
|
||||
.as('save_user_settings');
|
||||
// cy.get('.remove-filter').click();
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const doctype_name = custom_submittable_doctype.name;
|
|||
context('Report View', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/space/Website');
|
||||
cy.visit('/app/website');
|
||||
cy.insert_doc('DocType', custom_submittable_doctype, true);
|
||||
cy.clear_cache();
|
||||
cy.insert_doc(doctype_name, {
|
||||
|
|
@ -16,8 +16,7 @@ context('Report View', () => {
|
|||
}, true).as('doc');
|
||||
});
|
||||
it('Field with enabled allow_on_submit should be editable.', () => {
|
||||
cy.server();
|
||||
cy.route('POST', 'api/method/frappe.client.set_value').as('value-update');
|
||||
cy.intercept('POST', 'api/method/frappe.client.set_value').as('value-update');
|
||||
cy.visit(`/app/List/${doctype_name}/Report`);
|
||||
// check status column added from docstatus
|
||||
cy.get('.dt-row-0 > .dt-cell--col-3').should('contain', 'Submitted');
|
||||
|
|
|
|||
|
|
@ -17,11 +17,10 @@ context('Table MultiSelect', () => {
|
|||
.as('selected-value');
|
||||
cy.get('@selected-value').should('contain', 'test@erpnext.com');
|
||||
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
|
||||
cy.intercept('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
|
||||
// trigger save
|
||||
cy.get('.primary-action').click();
|
||||
cy.wait('@save_form').its('status').should('eq', 200);
|
||||
cy.wait('@save_form').its('response.statusCode').should('eq', 200);
|
||||
cy.get('@selected-value').should('contain', 'test@erpnext.com');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ Cypress.Commands.add('remove_doc', (doctype, name) => {
|
|||
|
||||
Cypress.Commands.add('create_records', doc => {
|
||||
return cy
|
||||
.call('frappe.tests.ui_test_helpers.create_if_not_exists', { doc })
|
||||
.call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc})
|
||||
.then(r => r.message);
|
||||
});
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
|
|||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, { waitForAnimations: false, force: true });
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true});
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
|
@ -204,15 +204,47 @@ Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => {
|
|||
return cy.get(selector);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => {
|
||||
cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as('input');
|
||||
|
||||
if (['Date', 'Time', 'Datetime'].includes(fieldtype)) {
|
||||
cy.get('@input').click().wait(200);
|
||||
cy.get('.datepickers-container .datepicker.active').should('exist');
|
||||
}
|
||||
if (fieldtype === 'Time') {
|
||||
cy.get('@input').clear().wait(200);
|
||||
}
|
||||
|
||||
if (fieldtype === 'Select') {
|
||||
cy.get('@input').select(value);
|
||||
} else {
|
||||
cy.get('@input').type(value, {waitForAnimations: false, force: true});
|
||||
}
|
||||
return cy.get('@input');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('get_table_field', (tablefieldname, row_idx, fieldname, fieldtype = 'Data') => {
|
||||
let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
|
||||
selector += ` [data-idx="${row_idx}"]`;
|
||||
selector += ` .form-in-grid`;
|
||||
|
||||
if (fieldtype === 'Text Editor') {
|
||||
selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
|
||||
} else if (fieldtype === 'Code') {
|
||||
selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
|
||||
} else {
|
||||
selector += ` .form-control[data-fieldname="${fieldname}"]`;
|
||||
}
|
||||
|
||||
return cy.get(selector);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('awesomebar', text => {
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, { delay: 100 });
|
||||
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, {delay: 100});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('new_form', doctype => {
|
||||
let dt_in_route = doctype.toLowerCase().replace(/ /g, '-')
|
||||
// // let route = `${dt_in_route}/new-${dt_in_route}-1`;
|
||||
// let route = `${dt_in_route}/new`;
|
||||
// let route = `${doctype.toLowerCase().replace(' ', '-')}/new`;
|
||||
let dt_in_route = doctype.toLowerCase().replace(/ /g, '-');
|
||||
cy.visit(`/app/${dt_in_route}/new`);
|
||||
cy.get('body').should('have.attr', 'data-route', `Form/${doctype}/new-${dt_in_route}-1`);
|
||||
cy.get('body').should('have.attr', 'data-ajax-state', 'complete');
|
||||
|
|
@ -244,8 +276,7 @@ Cypress.Commands.add('get_open_dialog', () => {
|
|||
|
||||
Cypress.Commands.add('hide_dialog', () => {
|
||||
cy.wait(200);
|
||||
cy.get_open_dialog()
|
||||
.find('.btn-modal-close').click()
|
||||
cy.get_open_dialog().find('.btn-modal-close').click();
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
|
|
@ -289,4 +320,8 @@ Cypress.Commands.add('clear_filters', () => {
|
|||
cy.wait(300);
|
||||
cy.get('.filter-popover').should('exist');
|
||||
cy.get('.filter-popover').find('.clear-filters').click();
|
||||
cy.get('.filter-section .filter-button').click();
|
||||
cy.window().its('cur_list').then(cur_list => {
|
||||
cur_list && cur_list.filter_area && cur_list.filter_area.clear();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,5 +21,5 @@ import './commands';
|
|||
// require('./commands')
|
||||
|
||||
Cypress.Cookies.defaults({
|
||||
whitelist: 'sid'
|
||||
preserve: 'sid'
|
||||
});
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
"""
|
||||
globals attached to frappe module
|
||||
+ some utility functions that should probably be moved
|
||||
Frappe - Low Code Open Source Framework in Python and JS
|
||||
|
||||
Frappe, pronounced fra-pay, is a full stack, batteries-included, web
|
||||
framework written in Python and Javascript with MariaDB as the database.
|
||||
It is the framework which powers ERPNext. It is pretty generic and can
|
||||
be used to build database driven apps.
|
||||
|
||||
Read the documentation: https://frappeframework.com/docs
|
||||
"""
|
||||
from __future__ import unicode_literals, print_function
|
||||
|
||||
|
|
@ -27,6 +33,7 @@ __version__ = '13.0.0-dev'
|
|||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
controllers = {}
|
||||
|
||||
class _dict(dict):
|
||||
"""dict like object that exposes keys as attributes"""
|
||||
|
|
@ -327,7 +334,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False,
|
|||
:param is_minimizable: [optional] Allow users to minimize the modal
|
||||
:param wide: [optional] Show wide modal
|
||||
"""
|
||||
from frappe.utils import encode
|
||||
from frappe.utils import strip_html_tags
|
||||
|
||||
msg = safe_decode(msg)
|
||||
out = _dict(message=msg)
|
||||
|
|
@ -354,7 +361,7 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, as_list=False,
|
|||
out.as_list = 1
|
||||
|
||||
if flags.print_messages and out.message:
|
||||
print(f"Message: {repr(out.message).encode('utf-8')}")
|
||||
print(f"Message: {strip_html_tags(out.message)}")
|
||||
|
||||
if title:
|
||||
out.title = title
|
||||
|
|
@ -465,7 +472,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
|
|||
attachments=None, content=None, doctype=None, name=None, reply_to=None,
|
||||
cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
|
||||
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
|
||||
inline_images=None, template=None, args=None, header=None, print_letterhead=False):
|
||||
inline_images=None, template=None, args=None, header=None, print_letterhead=False, with_container=False):
|
||||
"""Send email using user's default **Email Account** or global default **Email Account**.
|
||||
|
||||
|
||||
|
|
@ -491,6 +498,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
|
|||
:param template: Name of html template from templates/emails folder
|
||||
:param args: Arguments for rendering the template
|
||||
:param header: Append header in email
|
||||
:param with_container: Wraps email inside a styled container
|
||||
"""
|
||||
text_content = None
|
||||
if template:
|
||||
|
|
@ -513,7 +521,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
|
|||
attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to,
|
||||
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority,
|
||||
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
|
||||
inline_images=inline_images, header=header, print_letterhead=print_letterhead)
|
||||
inline_images=inline_images, header=header, print_letterhead=print_letterhead, with_container=with_container)
|
||||
|
||||
whitelisted = []
|
||||
guest_methods = []
|
||||
|
|
@ -628,6 +636,21 @@ def clear_cache(user=None, doctype=None):
|
|||
|
||||
local.role_permissions = {}
|
||||
|
||||
def only_has_select_perm(doctype, user=None, ignore_permissions=False):
|
||||
if ignore_permissions:
|
||||
return False
|
||||
|
||||
if not user:
|
||||
user = local.session.user
|
||||
|
||||
import frappe.permissions
|
||||
permissions = frappe.permissions.get_role_permissions(doctype, user=user)
|
||||
|
||||
if permissions.get('select') and not permissions.get('read'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False):
|
||||
"""Raises `frappe.PermissionError` if not permitted.
|
||||
|
||||
|
|
@ -948,10 +971,6 @@ def get_installed_apps(sort=False, frappe_last=False):
|
|||
if not local.all_apps:
|
||||
local.all_apps = cache().get_value('all_apps', get_all_apps)
|
||||
|
||||
#cache bench apps
|
||||
if not cache().get_value('all_apps'):
|
||||
cache().set_value('all_apps', local.all_apps)
|
||||
|
||||
installed = json.loads(db.get_global("installed_apps") or "[]")
|
||||
|
||||
if sort:
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import os
|
|||
from six import iteritems
|
||||
import logging
|
||||
|
||||
from werkzeug.wrappers import Request
|
||||
from werkzeug.local import LocalManager
|
||||
from werkzeug.wrappers import Request, Response
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
from werkzeug.middleware.profiler import ProfilerMiddleware
|
||||
from werkzeug.middleware.shared_data import SharedDataMiddleware
|
||||
|
|
@ -57,19 +57,22 @@ def application(request):
|
|||
frappe.monitor.start()
|
||||
frappe.rate_limiter.apply()
|
||||
|
||||
if frappe.local.form_dict.cmd:
|
||||
if request.method == "OPTIONS":
|
||||
response = Response()
|
||||
|
||||
elif frappe.form_dict.cmd:
|
||||
response = frappe.handler.handle()
|
||||
|
||||
elif frappe.request.path.startswith("/api/"):
|
||||
elif request.path.startswith("/api/"):
|
||||
response = frappe.api.handle()
|
||||
|
||||
elif frappe.request.path.startswith('/backups'):
|
||||
elif request.path.startswith('/backups'):
|
||||
response = frappe.utils.response.download_backup(request.path)
|
||||
|
||||
elif frappe.request.path.startswith('/private/files/'):
|
||||
elif request.path.startswith('/private/files/'):
|
||||
response = frappe.utils.response.download_private_file(request.path)
|
||||
|
||||
elif frappe.local.request.method in ('GET', 'HEAD', 'POST'):
|
||||
elif request.method in ('GET', 'HEAD', 'POST'):
|
||||
response = frappe.website.render.render()
|
||||
|
||||
else:
|
||||
|
|
@ -88,13 +91,9 @@ def application(request):
|
|||
rollback = after_request(rollback)
|
||||
|
||||
finally:
|
||||
if frappe.local.request.method in ("POST", "PUT") and frappe.db and rollback:
|
||||
if request.method in ("POST", "PUT") and frappe.db and rollback:
|
||||
frappe.db.rollback()
|
||||
|
||||
# set cookies
|
||||
if response and hasattr(frappe.local, 'cookie_manager'):
|
||||
frappe.local.cookie_manager.flush_cookies(response=response)
|
||||
|
||||
frappe.rate_limiter.update()
|
||||
frappe.monitor.stop(response)
|
||||
frappe.recorder.dump()
|
||||
|
|
@ -110,9 +109,7 @@ def application(request):
|
|||
"http_status_code": getattr(response, "status_code", "NOTFOUND")
|
||||
})
|
||||
|
||||
if response and hasattr(frappe.local, 'rate_limiter'):
|
||||
response.headers.extend(frappe.local.rate_limiter.headers())
|
||||
|
||||
process_response(response)
|
||||
frappe.destroy()
|
||||
|
||||
return response
|
||||
|
|
@ -134,7 +131,46 @@ def init_request(request):
|
|||
|
||||
make_form_dict(request)
|
||||
|
||||
frappe.local.http_request = frappe.auth.HTTPRequest()
|
||||
if request.method != "OPTIONS":
|
||||
frappe.local.http_request = frappe.auth.HTTPRequest()
|
||||
|
||||
def process_response(response):
|
||||
if not response:
|
||||
return
|
||||
|
||||
# set cookies
|
||||
if hasattr(frappe.local, 'cookie_manager'):
|
||||
frappe.local.cookie_manager.flush_cookies(response=response)
|
||||
|
||||
# rate limiter headers
|
||||
if hasattr(frappe.local, 'rate_limiter'):
|
||||
response.headers.extend(frappe.local.rate_limiter.headers())
|
||||
|
||||
# CORS headers
|
||||
if hasattr(frappe.local, 'conf') and frappe.conf.allow_cors:
|
||||
set_cors_headers(response)
|
||||
|
||||
def set_cors_headers(response):
|
||||
origin = frappe.request.headers.get('Origin')
|
||||
if not origin:
|
||||
return
|
||||
|
||||
allow_cors = frappe.conf.allow_cors
|
||||
if allow_cors != "*":
|
||||
if not isinstance(allow_cors, list):
|
||||
allow_cors = [allow_cors]
|
||||
|
||||
if origin not in allow_cors:
|
||||
return
|
||||
|
||||
response.headers.extend({
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': ('Authorization,DNT,X-Mx-ReqToken,'
|
||||
'Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,'
|
||||
'Cache-Control,Content-Type')
|
||||
})
|
||||
|
||||
def make_form_dict(request):
|
||||
import json
|
||||
|
|
|
|||
|
|
@ -54,10 +54,12 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
|
||||
toggle_submit_on_creation: function(frm) {
|
||||
// submit on creation checkbox
|
||||
frappe.model.with_doctype(frm.doc.reference_doctype, () => {
|
||||
let meta = frappe.get_meta(frm.doc.reference_doctype);
|
||||
frm.toggle_display('submit_on_creation', meta.is_submittable);
|
||||
});
|
||||
if (frm.doc.reference_doctype) {
|
||||
frappe.model.with_doctype(frm.doc.reference_doctype, () => {
|
||||
let meta = frappe.get_meta(frm.doc.reference_doctype);
|
||||
frm.toggle_display('submit_on_creation', meta.is_submittable);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
template: function(frm) {
|
||||
|
|
@ -100,10 +102,7 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
|
||||
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.call("get_auto_repeat_schedule").then(r => {
|
||||
frm.dashboard.wrapper.empty();
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template("auto_repeat_schedule", {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
"repeat_on_last_day",
|
||||
"column_break_12",
|
||||
"next_schedule_date",
|
||||
"section_break_16",
|
||||
"repeat_on_days",
|
||||
"notification",
|
||||
"notify_by_email",
|
||||
"recipients",
|
||||
|
|
@ -189,15 +191,27 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Repeat on Last Day of the Month"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.frequency==='Weekly';",
|
||||
"fieldname": "repeat_on_days",
|
||||
"fieldtype": "Table",
|
||||
"label": "Repeat on Days",
|
||||
"options": "Auto Repeat Day"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "submit_on_creation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit on Creation"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.frequency==='Weekly';",
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-12-10 10:43:13.449172",
|
||||
"modified": "2021-01-12 09:24:49.719611",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Auto Repeat",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from datetime import timedelta
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
|
@ -13,9 +14,12 @@ from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_
|
|||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
from frappe.automation.doctype.assignment_rule.assignment_rule import get_repeated
|
||||
from frappe.contacts.doctype.contact.contact import get_contacts_linked_from
|
||||
from frappe.contacts.doctype.contact.contact import get_contacts_linking_to
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
|
||||
week_map = {'Monday': 0, 'Tuesday': 1, 'Wednesday': 2, 'Thursday': 3, 'Friday': 4, 'Saturday': 5, 'Sunday': 6}
|
||||
|
||||
class AutoRepeat(Document):
|
||||
def validate(self):
|
||||
|
|
@ -24,6 +28,7 @@ class AutoRepeat(Document):
|
|||
self.validate_submit_on_creation()
|
||||
self.validate_dates()
|
||||
self.validate_email_id()
|
||||
self.validate_auto_repeat_days()
|
||||
self.set_dates()
|
||||
self.update_auto_repeat_id()
|
||||
self.unlink_if_applicable()
|
||||
|
|
@ -49,7 +54,7 @@ class AutoRepeat(Document):
|
|||
if self.disabled:
|
||||
self.next_schedule_date = None
|
||||
else:
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, self.end_date)
|
||||
self.next_schedule_date = self.get_next_schedule_date(schedule_date=self.start_date)
|
||||
|
||||
def unlink_if_applicable(self):
|
||||
if self.status == 'Completed' or self.disabled:
|
||||
|
|
@ -88,6 +93,12 @@ class AutoRepeat(Document):
|
|||
else:
|
||||
frappe.throw(_("'Recipients' not specified"))
|
||||
|
||||
def validate_auto_repeat_days(self):
|
||||
auto_repeat_days = self.get_auto_repeat_days()
|
||||
if not len(set(auto_repeat_days)) == len(auto_repeat_days):
|
||||
repeated_days = get_repeated(auto_repeat_days)
|
||||
frappe.throw(_('Auto Repeat Day {0} has been repeated.').format(frappe.bold(repeated_days)))
|
||||
|
||||
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")
|
||||
|
|
@ -113,7 +124,7 @@ class AutoRepeat(Document):
|
|||
end_date = getdate(self.end_date)
|
||||
|
||||
if not self.end_date:
|
||||
next_date = get_next_schedule_date(start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day)
|
||||
next_date = self.get_next_schedule_date(schedule_date=start_date)
|
||||
row = {
|
||||
"reference_document": self.reference_document,
|
||||
"frequency": self.frequency,
|
||||
|
|
@ -122,8 +133,7 @@ class AutoRepeat(Document):
|
|||
schedule_details.append(row)
|
||||
|
||||
if self.end_date:
|
||||
next_date = get_next_schedule_date(
|
||||
start_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, for_full_schedule=True)
|
||||
next_date = self.get_next_schedule_date(schedule_date=start_date, for_full_schedule=True)
|
||||
|
||||
while (getdate(next_date) < getdate(end_date)):
|
||||
row = {
|
||||
|
|
@ -132,8 +142,7 @@ class AutoRepeat(Document):
|
|||
"next_scheduled_date" : next_date
|
||||
}
|
||||
schedule_details.append(row)
|
||||
next_date = get_next_schedule_date(
|
||||
next_date, self.frequency, self.start_date, self.repeat_on_day, self.repeat_on_last_day, end_date, for_full_schedule=True)
|
||||
next_date = self.get_next_schedule_date(schedule_date=next_date, for_full_schedule=True)
|
||||
|
||||
return schedule_details
|
||||
|
||||
|
|
@ -211,6 +220,75 @@ class AutoRepeat(Document):
|
|||
new_doc.set('from_date', from_date)
|
||||
new_doc.set('to_date', to_date)
|
||||
|
||||
def get_next_schedule_date(self, schedule_date, for_full_schedule=False):
|
||||
"""
|
||||
Returns the next schedule date for auto repeat after a recurring document has been created.
|
||||
Adds required offset to the schedule_date param and returns the next schedule date.
|
||||
|
||||
:param schedule_date: The date when the last recurring document was created.
|
||||
:param for_full_schedule: If True, returns the immediate next schedule date, else the full schedule.
|
||||
"""
|
||||
if month_map.get(self.frequency):
|
||||
month_count = month_map.get(self.frequency) + month_diff(schedule_date, self.start_date) - 1
|
||||
else:
|
||||
month_count = 0
|
||||
|
||||
day_count = 0
|
||||
if month_count and self.repeat_on_last_day:
|
||||
day_count = 31
|
||||
next_date = get_next_date(self.start_date, month_count, day_count)
|
||||
elif month_count and self.repeat_on_day:
|
||||
day_count = self.repeat_on_day
|
||||
next_date = get_next_date(self.start_date, month_count, day_count)
|
||||
elif month_count:
|
||||
next_date = get_next_date(self.start_date, month_count)
|
||||
else:
|
||||
days = self.get_days(schedule_date)
|
||||
next_date = add_days(schedule_date, days)
|
||||
|
||||
# next schedule date should be after or on current date
|
||||
if not for_full_schedule:
|
||||
while getdate(next_date) < getdate(today()):
|
||||
if month_count:
|
||||
month_count += month_map.get(self.frequency, 0)
|
||||
next_date = get_next_date(self.start_date, month_count, day_count)
|
||||
else:
|
||||
days = self.get_days(next_date)
|
||||
next_date = add_days(next_date, days)
|
||||
|
||||
return next_date
|
||||
|
||||
def get_days(self, schedule_date):
|
||||
if self.frequency == "Weekly":
|
||||
days = self.get_offset_for_weekly_frequency(schedule_date)
|
||||
else:
|
||||
# daily frequency
|
||||
days = 1
|
||||
|
||||
return days
|
||||
|
||||
def get_offset_for_weekly_frequency(self, schedule_date):
|
||||
# if weekdays are not set, offset is 7 from current schedule date
|
||||
if not self.repeat_on_days:
|
||||
return 7
|
||||
|
||||
repeat_on_days = self.get_auto_repeat_days()
|
||||
current_schedule_day = getdate(schedule_date).weekday()
|
||||
weekdays = list(week_map.keys())
|
||||
|
||||
# if repeats on more than 1 day or
|
||||
# start date's weekday is not in repeat days, then get next weekday
|
||||
# else offset is 7
|
||||
if len(repeat_on_days) > 1 or weekdays[current_schedule_day] not in repeat_on_days:
|
||||
weekday = get_next_weekday(current_schedule_day, repeat_on_days)
|
||||
next_weekday_number = week_map.get(weekday, 0)
|
||||
# offset for upcoming weekday
|
||||
return timedelta((7 + next_weekday_number - current_schedule_day) % 7).days
|
||||
return 7
|
||||
|
||||
def get_auto_repeat_days(self):
|
||||
return [d.day for d in self.get('repeat_on_days', [])]
|
||||
|
||||
def send_notification(self, new_doc):
|
||||
"""Notify concerned people about recurring document generation"""
|
||||
subject = self.subject or ''
|
||||
|
|
@ -252,13 +330,8 @@ class AutoRepeat(Document):
|
|||
|
||||
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]
|
||||
])
|
||||
|
||||
res = get_contacts_linking_to(self.reference_doctype, self.reference_document, fields=['email_id'])
|
||||
res += get_contacts_linked_from(self.reference_doctype, self.reference_document, fields=['email_id'])
|
||||
email_ids = list(set([d.email_id for d in res]))
|
||||
if not email_ids:
|
||||
frappe.msgprint(_('No contacts linked to document'), alert=True)
|
||||
|
|
@ -291,42 +364,24 @@ class AutoRepeat(Document):
|
|||
)
|
||||
|
||||
|
||||
def get_next_schedule_date(schedule_date, frequency, start_date, repeat_on_day=None, repeat_on_last_day=False, end_date=None, for_full_schedule=False):
|
||||
if month_map.get(frequency):
|
||||
month_count = month_map.get(frequency) + month_diff(schedule_date, start_date) - 1
|
||||
else:
|
||||
month_count = 0
|
||||
|
||||
day_count = 0
|
||||
if month_count and repeat_on_last_day:
|
||||
day_count = 31
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif month_count and repeat_on_day:
|
||||
day_count = repeat_on_day
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif month_count:
|
||||
next_date = get_next_date(start_date, month_count)
|
||||
else:
|
||||
days = 7 if frequency == 'Weekly' else 1
|
||||
next_date = add_days(schedule_date, days)
|
||||
|
||||
# next schedule date should be after or on current date
|
||||
if not for_full_schedule:
|
||||
while getdate(next_date) < getdate(today()):
|
||||
if month_count:
|
||||
month_count += month_map.get(frequency)
|
||||
next_date = get_next_date(start_date, month_count, day_count)
|
||||
elif days:
|
||||
next_date = add_days(next_date, days)
|
||||
|
||||
return next_date
|
||||
|
||||
|
||||
def get_next_date(dt, mcount, day=None):
|
||||
dt = getdate(dt)
|
||||
dt += relativedelta(months=mcount, day=day)
|
||||
return dt
|
||||
|
||||
|
||||
def get_next_weekday(current_schedule_day, weekdays):
|
||||
days = list(week_map.keys())
|
||||
if current_schedule_day > 0:
|
||||
days = days[(current_schedule_day + 1):] + days[:current_schedule_day]
|
||||
else:
|
||||
days = days[(current_schedule_day + 1):]
|
||||
|
||||
for entry in days:
|
||||
if entry in weekdays:
|
||||
return entry
|
||||
|
||||
|
||||
#called through hooks
|
||||
def make_auto_repeat_entry():
|
||||
enqueued_method = 'frappe.automation.doctype.auto_repeat.auto_repeat.create_repeated_entries'
|
||||
|
|
@ -337,6 +392,7 @@ def make_auto_repeat_entry():
|
|||
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)
|
||||
|
|
@ -346,10 +402,11 @@ def create_repeated_entries(data):
|
|||
|
||||
if schedule_date == current_date and not doc.disabled:
|
||||
doc.create_documents()
|
||||
schedule_date = get_next_schedule_date(schedule_date, doc.frequency, doc.start_date, doc.repeat_on_day, doc.repeat_on_last_day, doc.end_date)
|
||||
schedule_date = doc.get_next_schedule_date(schedule_date=schedule_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())
|
||||
|
|
@ -358,6 +415,7 @@ def get_auto_repeat_entries(date=None):
|
|||
['status', '=', 'Active']
|
||||
])
|
||||
|
||||
|
||||
#called through hooks
|
||||
def set_auto_repeat_as_completed():
|
||||
auto_repeat = frappe.get_all("Auto Repeat", filters = {'status': ['!=', 'Disabled']})
|
||||
|
|
@ -367,6 +425,7 @@ def set_auto_repeat_as_completed():
|
|||
doc.status = 'Completed'
|
||||
doc.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = None, end_date = None):
|
||||
if not start_date:
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import unittest
|
|||
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.automation.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries
|
||||
from frappe.automation.doctype.auto_repeat.auto_repeat import get_auto_repeat_entries, create_repeated_entries, week_map
|
||||
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',
|
||||
|
|
@ -42,6 +41,52 @@ class TestAutoRepeat(unittest.TestCase):
|
|||
|
||||
self.assertEqual(todo.get('description'), new_todo.get('description'))
|
||||
|
||||
def test_weekly_auto_repeat(self):
|
||||
todo = frappe.get_doc(
|
||||
dict(doctype='ToDo', description='test weekly todo', assigned_by='Administrator')).insert()
|
||||
|
||||
doc = make_auto_repeat(reference_doctype='ToDo',
|
||||
frequency='Weekly', reference_document=todo.name, start_date=add_days(today(), -7))
|
||||
|
||||
self.assertEqual(doc.next_schedule_date, today())
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
todo = frappe.get_doc(doc.reference_doctype, doc.reference_document)
|
||||
self.assertEqual(todo.auto_repeat, doc.name)
|
||||
|
||||
new_todo = frappe.db.get_value('ToDo',
|
||||
{'auto_repeat': doc.name, 'name': ('!=', todo.name)}, 'name')
|
||||
|
||||
new_todo = frappe.get_doc('ToDo', new_todo)
|
||||
|
||||
self.assertEqual(todo.get('description'), new_todo.get('description'))
|
||||
|
||||
def test_weekly_auto_repeat_with_weekdays(self):
|
||||
todo = frappe.get_doc(
|
||||
dict(doctype='ToDo', description='test auto repeat with weekdays', assigned_by='Administrator')).insert()
|
||||
|
||||
weekdays = list(week_map.keys())
|
||||
current_weekday = getdate().weekday()
|
||||
days = [
|
||||
{'day': weekdays[current_weekday]},
|
||||
{'day': weekdays[(current_weekday + 2) % 7]}
|
||||
]
|
||||
doc = make_auto_repeat(reference_doctype='ToDo',
|
||||
frequency='Weekly', reference_document=todo.name, start_date=add_days(today(), -7), days=days)
|
||||
|
||||
self.assertEqual(doc.next_schedule_date, today())
|
||||
data = get_auto_repeat_entries(getdate(today()))
|
||||
create_repeated_entries(data)
|
||||
frappe.db.commit()
|
||||
|
||||
todo = frappe.get_doc(doc.reference_doctype, doc.reference_document)
|
||||
self.assertEqual(todo.auto_repeat, doc.name)
|
||||
|
||||
doc.reload()
|
||||
self.assertEqual(doc.next_schedule_date, add_days(getdate(), 2))
|
||||
|
||||
def test_monthly_auto_repeat(self):
|
||||
start_date = today()
|
||||
end_date = add_months(start_date, 12)
|
||||
|
|
@ -144,7 +189,8 @@ def make_auto_repeat(**args):
|
|||
'notify_by_email': args.notify or 0,
|
||||
'recipients': args.recipients or "",
|
||||
'subject': args.subject or "",
|
||||
'message': args.message or ""
|
||||
'message': args.message or "",
|
||||
'repeat_on_days': args.days or []
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
return doc
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-11-10 22:30:53.690228",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"day"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "day",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Day",
|
||||
"options": "Monday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-10 22:30:53.690228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Auto Repeat Day",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DeskShortcut(Document):
|
||||
class AutoRepeatDay(Document):
|
||||
pass
|
||||
|
|
@ -109,7 +109,7 @@ def load_conf_settings(bootinfo):
|
|||
|
||||
def load_desktop_data(bootinfo):
|
||||
from frappe.desk.desktop import get_desk_sidebar_items
|
||||
bootinfo.allowed_workspaces = get_desk_sidebar_items(flatten=True, cache=False)
|
||||
bootinfo.allowed_workspaces = get_desk_sidebar_items()
|
||||
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map()
|
||||
bootinfo.dashboards = frappe.get_all("Dashboard")
|
||||
|
||||
|
|
@ -250,13 +250,12 @@ def add_home_page(bootinfo, docs):
|
|||
|
||||
try:
|
||||
page = frappe.desk.desk_page.get(home_page)
|
||||
docs.append(page)
|
||||
bootinfo['home_page'] = page.name
|
||||
except (frappe.DoesNotExistError, frappe.PermissionError):
|
||||
if frappe.message_log:
|
||||
frappe.message_log.pop()
|
||||
page = frappe.desk.desk_page.get('space')
|
||||
|
||||
bootinfo['home_page'] = page.name
|
||||
docs.append(page)
|
||||
bootinfo['home_page'] = 'Workspaces'
|
||||
|
||||
def add_timezone_info(bootinfo):
|
||||
system = bootinfo.sysdefaults.get("time_zone")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ common_default_keys = ["__default", "__global"]
|
|||
doctype_map_keys = ('energy_point_rule_map', 'assignment_rule_map',
|
||||
'milestone_tracker_map', 'event_consumer_document_type_map')
|
||||
|
||||
global_cache_keys = ("app_hooks", "installed_apps",
|
||||
global_cache_keys = ("app_hooks", "installed_apps", 'all_apps',
|
||||
"app_modules", "module_app", "system_settings",
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
|
||||
|
|
@ -68,6 +68,7 @@ def clear_defaults_cache(user=None):
|
|||
frappe.cache().delete_key("defaults")
|
||||
|
||||
def clear_doctype_cache(doctype=None):
|
||||
clear_controller_cache(doctype)
|
||||
cache = frappe.cache()
|
||||
|
||||
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
|
||||
|
|
@ -99,6 +100,15 @@ def clear_doctype_cache(doctype=None):
|
|||
for name in doctype_cache_keys:
|
||||
cache.delete_value(name)
|
||||
|
||||
def clear_controller_cache(doctype=None):
|
||||
if not doctype:
|
||||
del frappe.controllers
|
||||
frappe.controllers = {}
|
||||
return
|
||||
|
||||
for site_controllers in frappe.controllers.values():
|
||||
site_controllers.pop(doctype, None)
|
||||
|
||||
def get_doctype_map(doctype, name, filters=None, order_by=None):
|
||||
cache = frappe.cache()
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
|
|
|
|||
|
|
@ -571,10 +571,11 @@ def run_ui_tests(context, app, headless=False):
|
|||
plugin_path = "{0}/cypress-file-upload".format(node_bin)
|
||||
|
||||
# check if cypress in path...if not, install it.
|
||||
if not (os.path.exists(cypress_path) or os.path.exists(plugin_path)):
|
||||
if not (os.path.exists(cypress_path) or os.path.exists(plugin_path)) \
|
||||
or not subprocess.getoutput("npm view cypress version").startswith("6."):
|
||||
# install cypress
|
||||
click.secho("Installing Cypress...", fg="yellow")
|
||||
frappe.commands.popen("yarn add cypress@3 cypress-file-upload@^3.1 --no-lockfile")
|
||||
frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile")
|
||||
|
||||
# run for headless mode
|
||||
run_or_open = 'run --browser chrome --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open'
|
||||
|
|
|
|||
|
|
@ -256,3 +256,27 @@ def get_contact_with_phone_number(number):
|
|||
def get_contact_name(email_id):
|
||||
contact = frappe.get_list("Contact Email", filters={"email_id": email_id}, fields=["parent"], limit=1)
|
||||
return contact[0].parent if contact else None
|
||||
|
||||
def get_contacts_linking_to(doctype, docname, fields=None):
|
||||
"""Return a list of contacts containing a link to the given document."""
|
||||
return frappe.get_list('Contact', fields=fields, filters=[
|
||||
['Dynamic Link', 'link_doctype', '=', doctype],
|
||||
['Dynamic Link', 'link_name', '=', docname]
|
||||
])
|
||||
|
||||
def get_contacts_linked_from(doctype, docname, fields=None):
|
||||
"""Return a list of contacts that are contained in (linked from) the given document."""
|
||||
link_fields = frappe.get_meta(doctype).get('fields', {
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Contact'
|
||||
})
|
||||
if not link_fields:
|
||||
return []
|
||||
|
||||
contact_names = frappe.get_value(doctype, docname, fieldname=[f.fieldname for f in link_fields])
|
||||
if not contact_names:
|
||||
return []
|
||||
|
||||
return frappe.get_list('Contact', fields=fields, filters={
|
||||
'name': ('in', contact_names)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ class Comment(Document):
|
|||
|
||||
def on_update(self):
|
||||
update_comment_in_doc(self)
|
||||
if self.is_new():
|
||||
self.notify_change('update')
|
||||
|
||||
def on_trash(self):
|
||||
self.remove_comment_from_cache()
|
||||
|
|
@ -163,7 +165,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
|
|||
try:
|
||||
# use sql, so that we do not mess with the timestamp
|
||||
frappe.db.sql("""update `tab{0}` set `_comments`=%s where name=%s""".format(reference_doctype), # nosec
|
||||
(json.dumps(_comments[-50:]), reference_name))
|
||||
(json.dumps(_comments[-100:]), reference_name))
|
||||
|
||||
except Exception as e:
|
||||
if frappe.db.is_column_missing(e) and getattr(frappe.local, 'request', None):
|
||||
|
|
|
|||
|
|
@ -99,8 +99,7 @@ frappe.ui.form.on("Communication", {
|
|||
}
|
||||
},
|
||||
|
||||
show_relink_dialog: function(frm){
|
||||
var lib = "frappe.email";
|
||||
show_relink_dialog: function(frm) {
|
||||
var d = new frappe.ui.Dialog ({
|
||||
title: __("Relink Communication"),
|
||||
fields: [{
|
||||
|
|
@ -138,8 +137,10 @@ frappe.ui.form.on("Communication", {
|
|||
}
|
||||
});
|
||||
},
|
||||
function () {
|
||||
frappe.show_alert({message:__('Document not Relinked'), 'indicator': 'info'})
|
||||
function() {
|
||||
frappe.show_alert({
|
||||
message: __('Document not Relinked'), 'indicator': 'info'
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2017-01-11 04:21:35.217943",
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
"column_break_2",
|
||||
"permlevel",
|
||||
"section_break_4",
|
||||
"select",
|
||||
"read",
|
||||
"write",
|
||||
"create",
|
||||
|
|
@ -211,9 +213,16 @@
|
|||
"fieldtype": "Data",
|
||||
"label": "Reference Document Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "select",
|
||||
"fieldtype": "Check",
|
||||
"label": "Select"
|
||||
}
|
||||
],
|
||||
"modified": "2019-10-31 16:58:16.157079",
|
||||
"links": [],
|
||||
"modified": "2020-12-03 15:20:48.296730",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Custom DocPerm",
|
||||
|
|
|
|||
|
|
@ -751,7 +751,7 @@ class Row:
|
|||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"message": _("{0} is a mandatory field asdadsf").format(id_field.label),
|
||||
"message": _("{0} is a mandatory field").format(id_field.label),
|
||||
}
|
||||
)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -9,15 +9,16 @@ frappe.listview_settings["Deleted Document"] = {
|
|||
args: { docnames },
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
function body(docnames) {
|
||||
let body = (docnames) => {
|
||||
const html = docnames.map(docname => {
|
||||
return `<li><a href='/app/deleted-document/${docname}'>${docname}</a></li>`;
|
||||
});
|
||||
return "<br><ul>" + html.join("");
|
||||
}
|
||||
function message(title, docnames) {
|
||||
};
|
||||
|
||||
let message = (title, docnames) => {
|
||||
return (docnames.length > 0) ? title + body(docnames) + "</ul>": "";
|
||||
}
|
||||
};
|
||||
|
||||
const { restored, invalid, failed } = r.message;
|
||||
const restored_summary = message(__("Documents restored successfully"), restored);
|
||||
|
|
|
|||
|
|
@ -1,775 +1,229 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"beta": 0,
|
||||
"creation": "2013-02-22 01:27:33",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"role_and_level",
|
||||
"role",
|
||||
"if_owner",
|
||||
"column_break_2",
|
||||
"permlevel",
|
||||
"section_break_4",
|
||||
"select",
|
||||
"read",
|
||||
"write",
|
||||
"create",
|
||||
"delete",
|
||||
"column_break_8",
|
||||
"submit",
|
||||
"cancel",
|
||||
"amend",
|
||||
"additional_permissions",
|
||||
"report",
|
||||
"export",
|
||||
"import",
|
||||
"set_user_permissions",
|
||||
"column_break_19",
|
||||
"share",
|
||||
"print",
|
||||
"email"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "role_and_level",
|
||||
"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": "Role and Level",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Role and Level"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "role",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Role",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "role",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Role",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "150px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "Apply this rule if the User is the Owner",
|
||||
"fieldname": "if_owner",
|
||||
"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": "If user is the owner",
|
||||
"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": "If user is the owner"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"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": "Level",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "40px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "40px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_4",
|
||||
"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": "Permissions",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Permissions"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "read",
|
||||
"fieldtype": "Check",
|
||||
"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": "Read",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "read",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "write",
|
||||
"fieldtype": "Check",
|
||||
"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": "Write",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "write",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "create",
|
||||
"fieldtype": "Check",
|
||||
"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": "Create",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "create",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "delete",
|
||||
"fieldtype": "Check",
|
||||
"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": "Delete",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Delete"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "submit",
|
||||
"fieldtype": "Check",
|
||||
"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": "Submit",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "submit",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "cancel",
|
||||
"fieldtype": "Check",
|
||||
"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": "Cancel",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "cancel",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "amend",
|
||||
"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": "Amend",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "amend",
|
||||
"oldfieldtype": "Check",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "additional_permissions",
|
||||
"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": "Additional Permissions",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Additional Permissions"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "report",
|
||||
"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": "Report",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": "32px",
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "32px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "export",
|
||||
"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": "Export",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Export"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "import",
|
||||
"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": "Import",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Import"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"description": "This role update User Permissions for a user",
|
||||
"fieldname": "set_user_permissions",
|
||||
"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": "Set User Permissions",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Set User Permissions"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "share",
|
||||
"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": "Share",
|
||||
"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": "Share"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "print",
|
||||
"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": "Print",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Print"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "email",
|
||||
"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": "Email",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "select",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Select"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-05-29 11:54:38.613936",
|
||||
"links": [],
|
||||
"modified": "2020-12-03 15:15:30.488212",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocPerm",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
import re, copy, os, shutil
|
||||
import json
|
||||
from frappe.cache_manager import clear_user_cache
|
||||
from frappe.cache_manager import clear_user_cache, clear_controller_cache
|
||||
|
||||
# imports - third party imports
|
||||
import six
|
||||
|
|
@ -26,6 +26,7 @@ from frappe.database.schema import validate_column_name, validate_column_length
|
|||
from frappe.model.docfield import supports_translation
|
||||
from frappe.modules.import_file import get_file_path
|
||||
from frappe.model.meta import Meta
|
||||
from frappe.desk.utils import validate_route_conflict
|
||||
|
||||
class InvalidFieldNameError(frappe.ValidationError): pass
|
||||
class UniqueFieldnameError(frappe.ValidationError): pass
|
||||
|
|
@ -288,9 +289,15 @@ class DocType(Document):
|
|||
|
||||
self.update_fields_to_fetch()
|
||||
|
||||
from frappe import conf
|
||||
allow_doctype_export = frappe.flags.allow_doctype_export or (not frappe.flags.in_test and conf.get('developer_mode'))
|
||||
if not self.custom and not frappe.flags.in_import and allow_doctype_export:
|
||||
allow_doctype_export = (
|
||||
not self.custom
|
||||
and not frappe.flags.in_import
|
||||
and (
|
||||
frappe.conf.developer_mode
|
||||
or frappe.flags.allow_doctype_export
|
||||
)
|
||||
)
|
||||
if allow_doctype_export:
|
||||
self.export_doc()
|
||||
self.make_controller_template()
|
||||
|
||||
|
|
@ -368,13 +375,10 @@ class DocType(Document):
|
|||
if merge:
|
||||
frappe.throw(_("DocType can not be merged"))
|
||||
|
||||
# Do not rename and move files and folders for custom doctype
|
||||
if not self.custom and not frappe.flags.in_test and not frappe.flags.in_patch:
|
||||
self.rename_files_and_folders(old, new)
|
||||
|
||||
def after_rename(self, old, new, merge=False):
|
||||
"""Change table name using `RENAME TABLE` if table exists. Or update
|
||||
`doctype` property for Single type."""
|
||||
|
||||
if self.issingle:
|
||||
frappe.db.sql("""update tabSingles set doctype=%s where doctype=%s""", (new, old))
|
||||
frappe.db.sql("""update tabSingles set value=%s
|
||||
|
|
@ -384,6 +388,18 @@ class DocType(Document):
|
|||
"mariadb": f"RENAME TABLE `tab{old}` TO `tab{new}`",
|
||||
"postgres": f"ALTER TABLE `tab{old}` RENAME TO `tab{new}`"
|
||||
})
|
||||
frappe.db.commit()
|
||||
|
||||
# Do not rename and move files and folders for custom doctype
|
||||
if not self.custom:
|
||||
if not frappe.flags.in_patch:
|
||||
self.rename_files_and_folders(old, new)
|
||||
|
||||
clear_controller_cache(old)
|
||||
|
||||
def after_delete(self):
|
||||
if not self.custom:
|
||||
clear_controller_cache(self.name)
|
||||
|
||||
def rename_files_and_folders(self, old, new):
|
||||
# move files
|
||||
|
|
@ -640,7 +656,7 @@ class DocType(Document):
|
|||
flags = {"flags": re.ASCII} if six.PY3 else {}
|
||||
|
||||
# a DocType name should not start or end with an empty space
|
||||
if re.match("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags):
|
||||
if re.search("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags):
|
||||
frappe.throw(_("DocType's name should not start or end with whitespace"), frappe.NameError)
|
||||
|
||||
# a DocType's name should not start with a number or underscore
|
||||
|
|
@ -648,6 +664,8 @@ class DocType(Document):
|
|||
if not re.match("^(?![\W])[^\d_\s][\w ]+$", name, **flags):
|
||||
frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError)
|
||||
|
||||
validate_route_conflict(self.doctype, self.name)
|
||||
|
||||
def validate_links_table_fieldnames(meta):
|
||||
"""Validate fieldnames in Links table"""
|
||||
if frappe.flags.in_patch: return
|
||||
|
|
@ -984,10 +1002,10 @@ def validate_fields(meta):
|
|||
check_sort_field(meta)
|
||||
check_image_field(meta)
|
||||
|
||||
def validate_permissions_for_doctype(doctype, for_remove=False):
|
||||
def validate_permissions_for_doctype(doctype, for_remove=False, alert=False):
|
||||
"""Validates if permissions are set correctly."""
|
||||
doctype = frappe.get_doc("DocType", doctype)
|
||||
validate_permissions(doctype, for_remove)
|
||||
validate_permissions(doctype, for_remove, alert=alert)
|
||||
|
||||
# save permissions
|
||||
for perm in doctype.get("permissions"):
|
||||
|
|
@ -1010,9 +1028,10 @@ def clear_permissions_cache(doctype):
|
|||
""", doctype):
|
||||
frappe.clear_cache(user=user)
|
||||
|
||||
def validate_permissions(doctype, for_remove=False):
|
||||
def validate_permissions(doctype, for_remove=False, alert=False):
|
||||
permissions = doctype.get("permissions")
|
||||
if not permissions:
|
||||
# Some DocTypes may not have permissions by default, don't show alert for them
|
||||
if not permissions and alert:
|
||||
frappe.msgprint(_('No Permissions Specified'), alert=True, indicator='orange')
|
||||
issingle = issubmittable = isimportable = False
|
||||
if doctype:
|
||||
|
|
@ -1024,7 +1043,7 @@ def validate_permissions(doctype, for_remove=False):
|
|||
return _("For {0} at level {1} in {2} in row {3}").format(d.role, d.permlevel, d.parent, d.idx)
|
||||
|
||||
def check_atleast_one_set(d):
|
||||
if not d.read and not d.write and not d.submit and not d.cancel and not d.create:
|
||||
if not d.select and not d.read and not d.write and not d.submit and not d.cancel and not d.create:
|
||||
frappe.throw(_("{0}: No basic permissions set").format(get_txt(d)))
|
||||
|
||||
def check_double(d):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import frappe
|
||||
from frappe.desk.utils import get_doctype_route
|
||||
from frappe.desk.utils import slug
|
||||
|
||||
def execute():
|
||||
for doctype in frappe.get_all('DocType', ['name', 'route'], dict(istable=0)):
|
||||
if not doctype.route:
|
||||
frappe.db.set_value('DocType', doctype.name, 'route', get_doctype_route(doctype.name), update_modified = False)
|
||||
frappe.db.set_value('DocType', doctype.name, 'route', slug(doctype.name), update_modified = False)
|
||||
|
|
@ -5,14 +5,13 @@ from __future__ import unicode_literals
|
|||
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.core.doctype.doctype.doctype import (UniqueFieldnameError,
|
||||
IllegalMandatoryError,
|
||||
DoctypeLinkError,
|
||||
WrongOptionsDoctypeLinkError,
|
||||
HiddenAndMandatoryWithoutDefaultError,
|
||||
CannotIndexedError,
|
||||
InvalidFieldNameError,
|
||||
CannotCreateStandardDoctypeError,
|
||||
from frappe.core.doctype.doctype.doctype import (UniqueFieldnameError,
|
||||
IllegalMandatoryError,
|
||||
DoctypeLinkError,
|
||||
WrongOptionsDoctypeLinkError,
|
||||
HiddenAndMandatoryWithoutDefaultError,
|
||||
CannotIndexedError,
|
||||
InvalidFieldNameError,
|
||||
validate_links_table_fieldnames)
|
||||
|
||||
# test_records = frappe.get_test_records('DocType')
|
||||
|
|
|
|||
|
|
@ -6,8 +6,19 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.data import evaluate_filters
|
||||
from frappe import _
|
||||
|
||||
class DocumentNamingRule(Document):
|
||||
def validate(self):
|
||||
self.validate_fields_in_conditions()
|
||||
|
||||
def validate_fields_in_conditions(self):
|
||||
if self.has_value_changed("document_type"):
|
||||
docfields = [x.fieldname for x in frappe.get_meta(self.document_type).fields]
|
||||
for condition in self.conditions:
|
||||
if condition.field not in docfields:
|
||||
frappe.throw(_("{0} is not a field of doctype {1}").format(frappe.bold(condition.field), frappe.bold(self.document_type)))
|
||||
|
||||
def apply(self, doc):
|
||||
'''
|
||||
Apply naming rules for the given document. Will set `name` if the rule is matched.
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ class File(Document):
|
|||
def save_file(self, content=None, decode=False, ignore_existing_file_check=False):
|
||||
file_exists = False
|
||||
self.content = content
|
||||
|
||||
|
||||
if decode:
|
||||
if isinstance(content, text_type):
|
||||
self.content = content.encode("utf-8")
|
||||
|
|
@ -467,19 +467,19 @@ class File(Document):
|
|||
|
||||
if not self.is_private:
|
||||
self.is_private = 0
|
||||
|
||||
|
||||
self.content_type = mimetypes.guess_type(self.file_name)[0]
|
||||
|
||||
|
||||
self.file_size = self.check_max_file_size()
|
||||
|
||||
|
||||
if (
|
||||
self.content_type and "image" in self.content_type
|
||||
and frappe.get_system_settings("strip_exif_metadata_from_uploaded_images")
|
||||
):
|
||||
self.content = strip_exif_data(self.content, self.content_type)
|
||||
self.content = strip_exif_data(self.content, self.content_type)
|
||||
|
||||
self.content_hash = get_content_hash(self.content)
|
||||
|
||||
|
||||
duplicate_file = None
|
||||
|
||||
# check if a file exists with the same content hash and is also in the same folder (public or private)
|
||||
|
|
@ -940,10 +940,33 @@ def validate_filename(filename):
|
|||
return fname
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_files_in_folder(folder):
|
||||
return frappe.db.get_all('File',
|
||||
def get_files_in_folder(folder, start=0, page_length=20):
|
||||
start = cint(start)
|
||||
page_length = cint(page_length)
|
||||
|
||||
files = frappe.db.get_all('File',
|
||||
{ 'folder': folder },
|
||||
['name', 'file_name', 'file_url', 'is_folder', 'modified']
|
||||
['name', 'file_name', 'file_url', 'is_folder', 'modified'],
|
||||
start=start,
|
||||
page_length=page_length + 1
|
||||
)
|
||||
return {
|
||||
'files': files[:page_length],
|
||||
'has_more': len(files) > page_length
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_files_by_search_text(text):
|
||||
if not text:
|
||||
return []
|
||||
|
||||
text = '%' + cstr(text).lower() + '%'
|
||||
return frappe.db.get_all('File',
|
||||
fields=['name', 'file_name', 'file_url', 'is_folder', 'modified'],
|
||||
filters={'is_folder': False},
|
||||
or_filters={'file_name': ('like', text), 'file_url': text, 'name': ('like', text)},
|
||||
order_by='modified desc',
|
||||
limit=20
|
||||
)
|
||||
|
||||
def update_existing_file_docs(doc):
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class ModuleDef(Document):
|
|||
def on_trash(self):
|
||||
"""Delete module name from modules.txt"""
|
||||
|
||||
if frappe.flags.in_uninstall or self.custom:
|
||||
if not frappe.conf.get('developer_mode') or frappe.flags.in_uninstall or self.custom:
|
||||
return
|
||||
|
||||
modules = None
|
||||
|
|
|
|||
19
frappe/core/doctype/module_profile/module_profile.js
Normal file
19
frappe/core/doctype/module_profile/module_profile.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Module Profile', {
|
||||
refresh: function(frm) {
|
||||
if (has_common(frappe.user_roles, ["Administrator", "System Manager"])) {
|
||||
if (!frm.module_editor && frm.doc.__onload && frm.doc.__onload.all_modules) {
|
||||
let module_area = $('<div style="min-height: 300px">')
|
||||
.appendTo(frm.fields_dict.module_html.wrapper);
|
||||
|
||||
frm.module_editor = new frappe.ModuleEditor(frm, module_area);
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.module_editor) {
|
||||
frm.module_editor.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
60
frappe/core/doctype/module_profile/module_profile.json
Normal file
60
frappe/core/doctype/module_profile/module_profile.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:module_profile_name",
|
||||
"creation": "2020-12-22 22:00:30.614475",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"module_profile_name",
|
||||
"module_html",
|
||||
"block_modules"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "module_profile_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Module Profile Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "module_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Module HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "block_modules",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 1,
|
||||
"label": "Block Modules",
|
||||
"options": "Block Module",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-03 15:36:52.622696",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Module Profile",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
12
frappe/core/doctype/module_profile/module_profile.py
Normal file
12
frappe/core/doctype/module_profile/module_profile.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ModuleProfile(Document):
|
||||
def onload(self):
|
||||
from frappe.config import get_modules_from_all_apps
|
||||
self.set_onload('all_modules',
|
||||
[m.get("module_name") for m in get_modules_from_all_apps()])
|
||||
32
frappe/core/doctype/module_profile/test_module_profile.py
Normal file
32
frappe/core/doctype/module_profile/test_module_profile.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestModuleProfile(unittest.TestCase):
|
||||
def test_make_new_module_profile(self):
|
||||
if not frappe.db.get_value('Module Profile', '_Test Module Profile'):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Module Profile',
|
||||
'module_profile_name': '_Test Module Profile',
|
||||
'block_modules': [
|
||||
{'module': 'Accounts'}
|
||||
]
|
||||
}).insert()
|
||||
|
||||
# add to user and check
|
||||
if not frappe.db.get_value('User', 'test-for-module_profile@example.com'):
|
||||
new_user = frappe.get_doc({
|
||||
'doctype': 'User',
|
||||
'email':'test-for-module_profile@example.com',
|
||||
'first_name':'Test User'
|
||||
}).insert()
|
||||
else:
|
||||
new_user = frappe.get_doc('User', 'test-for-module_profile@example.com')
|
||||
|
||||
new_user.module_profile = '_Test Module Profile'
|
||||
new_user.save()
|
||||
|
||||
self.assertEqual(new_user.block_modules[0].module, 'Accounts')
|
||||
|
|
@ -9,6 +9,7 @@ from frappe.build import html_to_js_template
|
|||
from frappe.model.utils import render_include
|
||||
from frappe import conf, _, safe_decode
|
||||
from frappe.desk.form.meta import get_code_files_via_hooks, get_js
|
||||
from frappe.desk.utils import validate_route_conflict
|
||||
from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles
|
||||
from six import text_type
|
||||
|
||||
|
|
@ -33,10 +34,7 @@ class Page(Document):
|
|||
self.name += '-' + str(cnt)
|
||||
|
||||
def validate(self):
|
||||
if frappe.db.get_value('DocType', self.name):
|
||||
frappe.throw(
|
||||
_("{} is the name of a DocType. DocType names cannot be the same as a Page name, please choose another name.").format(self.page_name)
|
||||
)
|
||||
validate_route_conflict(self.doctype, self.name)
|
||||
|
||||
if self.is_new() and not getattr(conf,'developer_mode', 0):
|
||||
frappe.throw(_("Not in Developer Mode"))
|
||||
|
|
|
|||
5
frappe/core/doctype/page/patches/drop_unused_pages.py
Normal file
5
frappe/core/doctype/page/patches/drop_unused_pages.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import frappe
|
||||
|
||||
def execute():
|
||||
for name in ('desktop', 'space'):
|
||||
frappe.delete_doc('Page', name)
|
||||
|
|
@ -8,4 +8,6 @@ import unittest
|
|||
test_records = frappe.get_test_records('Page')
|
||||
|
||||
class TestPage(unittest.TestCase):
|
||||
pass
|
||||
def test_naming(self):
|
||||
self.assertRaises(frappe.NameError, frappe.get_doc(dict(doctype='Page', page_name='DocType', module='Core')).insert)
|
||||
self.assertRaises(frappe.NameError, frappe.get_doc(dict(doctype='Page', page_name='Settings', module='Core')).insert)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Options"
|
||||
},
|
||||
{
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-17 16:15:46.937267",
|
||||
"modified": "2020-12-05 19:20:00.503097",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report Filter",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import frappe
|
|||
from ..role import desk_properties
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('role')
|
||||
for role in frappe.get_all('Role', ['name', 'desk_access']):
|
||||
role_doc = frappe.get_doc('Role', role.name)
|
||||
for key in desk_properties:
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ class Role(Document):
|
|||
|
||||
def set_desk_properties(self):
|
||||
# set if desk_access is not allowed, unset all desk properties
|
||||
if self.name == 'Guest':
|
||||
self.desk_access = 0
|
||||
|
||||
if not self.desk_access:
|
||||
for key in desk_properties:
|
||||
self.set(key, 0)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"fieldname": "doctype_event",
|
||||
"fieldtype": "Select",
|
||||
"label": "DocType Event",
|
||||
"options": "Before Insert\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)"
|
||||
"options": "Before Insert\nBefore Validate\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.script_type==='API'",
|
||||
|
|
@ -88,7 +88,7 @@
|
|||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 22:42:02.708148",
|
||||
"modified": "2021-01-03 18:50:14.767595",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import frappe
|
|||
EVENT_MAP = {
|
||||
'before_insert': 'Before Insert',
|
||||
'after_insert': 'After Insert',
|
||||
'before_validate': 'Before Validate',
|
||||
'validate': 'Before Save',
|
||||
'on_update': 'After Save',
|
||||
'before_submit': 'Before Submit',
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class TestServerScript(unittest.TestCase):
|
|||
def tearDownClass(cls):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql('truncate `tabServer Script`')
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
|
||||
def setUp(self):
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@
|
|||
"collapsible": 1,
|
||||
"fieldname": "email",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "EMail"
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"description": "Your organization name and address for the email footer.",
|
||||
|
|
@ -486,7 +486,7 @@
|
|||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-30 18:52:22.161391",
|
||||
"modified": "2020-12-30 18:52:22.161391",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
@ -504,4 +504,4 @@
|
|||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,25 @@ frappe.ui.form.on('User', {
|
|||
}
|
||||
},
|
||||
|
||||
module_profile: function(frm) {
|
||||
if (frm.doc.module_profile) {
|
||||
frappe.call({
|
||||
"method": "frappe.core.doctype.user.user.get_module_profile",
|
||||
args: {
|
||||
module_profile: frm.doc.module_profile
|
||||
},
|
||||
callback: function(data) {
|
||||
frm.set_value("block_modules", []);
|
||||
$.each(data.message || [], function(i, v) {
|
||||
let d = frm.add_child("block_modules");
|
||||
d.module = v.module;
|
||||
});
|
||||
frm.module_editor && frm.module_editor.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.can_edit_roles = has_access_to_edit_user();
|
||||
|
||||
|
|
@ -255,43 +274,3 @@ function get_roles_for_editing_user() {
|
|||
.filter(perm => perm.permlevel >= 1 && perm.write)
|
||||
.map(perm => perm.role) || ['System Manager'];
|
||||
}
|
||||
|
||||
frappe.ModuleEditor = Class.extend({
|
||||
init: function(frm, wrapper) {
|
||||
this.wrapper = $('<div class="row module-block-list"></div>').appendTo(wrapper);
|
||||
this.frm = frm;
|
||||
this.make();
|
||||
},
|
||||
make: function() {
|
||||
var me = this;
|
||||
this.frm.doc.__onload.all_modules.forEach(function(m) {
|
||||
$(repl('<div class="col-sm-4"><div class="checkbox">\
|
||||
<label><input type="checkbox" class="block-module-check" data-module="%(module)s">\
|
||||
%(module)s</label></div></div>', {module: m})).appendTo(me.wrapper);
|
||||
});
|
||||
this.bind();
|
||||
},
|
||||
refresh: function() {
|
||||
var me = this;
|
||||
this.wrapper.find(".block-module-check").prop("checked", true);
|
||||
$.each(this.frm.doc.block_modules, function(i, d) {
|
||||
me.wrapper.find(".block-module-check[data-module='"+ d.module +"']").prop("checked", false);
|
||||
});
|
||||
},
|
||||
bind: function() {
|
||||
var me = this;
|
||||
this.wrapper.on("change", ".block-module-check", function() {
|
||||
var module = $(this).attr('data-module');
|
||||
if($(this).prop("checked")) {
|
||||
// remove from block_modules
|
||||
me.frm.doc.block_modules = $.map(me.frm.doc.block_modules || [], function(d) {
|
||||
if (d.module != module) {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
me.frm.add_child("block_modules", {"module": module});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"allowed_in_mentions",
|
||||
"user_emails",
|
||||
"sb_allow_modules",
|
||||
"module_profile",
|
||||
"modules_html",
|
||||
"block_modules",
|
||||
"home_settings",
|
||||
|
|
@ -301,7 +302,7 @@
|
|||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"fieldname": "logout_all_sessions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Logout From All Devices After Changing Password"
|
||||
|
|
@ -594,6 +595,12 @@
|
|||
"fieldtype": "Select",
|
||||
"label": "Desk Theme",
|
||||
"options": "Light\nDark"
|
||||
},
|
||||
{
|
||||
"fieldname": "module_profile",
|
||||
"fieldtype": "Link",
|
||||
"label": "Module Profile",
|
||||
"options": "Module Profile"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
|
|
@ -654,10 +661,15 @@
|
|||
"group": "Activity",
|
||||
"link_doctype": "ToDo",
|
||||
"link_fieldname": "owner"
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"link_doctype": "Token Cache",
|
||||
"link_fieldname": "user"
|
||||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-12-24 19:48:49.677800",
|
||||
"modified": "2021-02-01 16:11:06.037543",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ class User(Document):
|
|||
self.validate_user_email_inbox()
|
||||
ask_pass_update()
|
||||
self.validate_roles()
|
||||
self.validate_allowed_modules()
|
||||
self.validate_user_image()
|
||||
|
||||
if self.language == "Loading...":
|
||||
|
|
@ -89,6 +90,15 @@ class User(Document):
|
|||
self.set('roles', [])
|
||||
self.append_roles(*[role.role for role in role_profile.roles])
|
||||
|
||||
def validate_allowed_modules(self):
|
||||
if self.module_profile:
|
||||
module_profile = frappe.get_doc('Module Profile', self.module_profile)
|
||||
self.set('block_modules', [])
|
||||
for d in module_profile.get('block_modules'):
|
||||
self.append('block_modules', {
|
||||
'module': d.module
|
||||
})
|
||||
|
||||
def validate_user_image(self):
|
||||
if self.user_image and len(self.user_image) > 2000:
|
||||
frappe.throw(_("Not a valid User Image."))
|
||||
|
|
@ -98,16 +108,17 @@ class User(Document):
|
|||
self.share_with_self()
|
||||
clear_notifications(user=self.name)
|
||||
frappe.clear_cache(user=self.name)
|
||||
now=frappe.flags.in_test or frappe.flags.in_install
|
||||
self.send_password_notification(self.__new_password)
|
||||
frappe.enqueue(
|
||||
'frappe.core.doctype.user.user.create_contact',
|
||||
user=self,
|
||||
ignore_mandatory=True,
|
||||
now=frappe.flags.in_test or frappe.flags.in_install
|
||||
now=now
|
||||
)
|
||||
if self.name not in ('Administrator', 'Guest') and not self.user_image:
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name)
|
||||
|
||||
frappe.enqueue('frappe.core.doctype.user.user.update_gravatar', name=self.name, now=now)
|
||||
|
||||
# Set user selected timezone
|
||||
if self.time_zone:
|
||||
frappe.defaults.set_default("time_zone", self.time_zone, self.name)
|
||||
|
|
@ -288,16 +299,16 @@ class User(Document):
|
|||
from frappe.utils.user import get_user_fullname
|
||||
from frappe.utils import get_url
|
||||
|
||||
full_name = get_user_fullname(frappe.session['user'])
|
||||
if full_name == "Guest":
|
||||
full_name = "Administrator"
|
||||
created_by = get_user_fullname(frappe.session['user'])
|
||||
if created_by == "Guest":
|
||||
created_by = "Administrator"
|
||||
|
||||
args = {
|
||||
'first_name': self.first_name or self.last_name or "user",
|
||||
'user': self.name,
|
||||
'title': subject,
|
||||
'login_url': get_url(),
|
||||
'user_fullname': full_name
|
||||
'created_by': created_by
|
||||
}
|
||||
|
||||
args.update(add_args)
|
||||
|
|
@ -551,6 +562,10 @@ def get_perm_info(role):
|
|||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def update_password(new_password, logout_all_sessions=0, key=None, old_password=None):
|
||||
#validate key to avoid key input like ['like', '%'], '', ['in', ['']]
|
||||
if key and not isinstance(key, str):
|
||||
frappe.throw(_('Invalid key type'))
|
||||
|
||||
result = test_password_strength(new_password, key, old_password)
|
||||
feedback = result.get("feedback", None)
|
||||
|
||||
|
|
@ -1002,9 +1017,14 @@ def send_token_via_email(tmp_id,token=None):
|
|||
hotp = pyotp.HOTP(otpsecret)
|
||||
|
||||
frappe.sendmail(
|
||||
recipients=user_email, sender=None, subject='Verification Code',
|
||||
message='<p>Your verification code is {0}</p>'.format(hotp.at(int(count))),
|
||||
delayed=False, retry=3)
|
||||
recipients=user_email,
|
||||
sender=None,
|
||||
subject="Verification Code",
|
||||
template="verification_code",
|
||||
args=dict(code=hotp.at(int(count))),
|
||||
delayed=False,
|
||||
retry=3
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -1038,6 +1058,11 @@ def get_role_profile(role_profile):
|
|||
roles = frappe.get_doc('Role Profile', {'role_profile': role_profile})
|
||||
return roles.roles
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_module_profile(module_profile):
|
||||
module_profile = frappe.get_doc('Module Profile', {'module_profile_name': module_profile})
|
||||
return module_profile.get('block_modules')
|
||||
|
||||
def update_roles(role_profile):
|
||||
users = frappe.get_all('User', filters={'role_profile_name': role_profile})
|
||||
role_profile = frappe.get_doc('Role Profile', role_profile)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
from frappe.core.doctype.user_permission.user_permission import add_user_permissions
|
||||
from frappe.permissions import has_user_permission
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
|
@ -10,7 +11,12 @@ import unittest
|
|||
class TestUserPermission(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("""DELETE FROM `tabUser Permission`
|
||||
WHERE `user` in ('test_bulk_creation_update@example.com', 'test_user_perm1@example.com')""")
|
||||
WHERE `user` in (
|
||||
'test_bulk_creation_update@example.com',
|
||||
'test_user_perm1@example.com',
|
||||
'nested_doc_user@example.com')""")
|
||||
frappe.delete_doc_if_exists("DocType", "Person")
|
||||
frappe.db.sql_ddl("DROP TABLE IF EXISTS `tabPerson`")
|
||||
|
||||
def test_default_user_permission_validation(self):
|
||||
user = create_user('test_default_permission@example.com')
|
||||
|
|
@ -108,6 +114,45 @@ class TestUserPermission(unittest.TestCase):
|
|||
self.assertIsNone(removed_applicable_second)
|
||||
self.assertEquals(is_created, 1)
|
||||
|
||||
def test_user_perm_for_nested_doctype(self):
|
||||
"""Test if descendants' visibility is controlled for a nested DocType."""
|
||||
from frappe.core.doctype.doctype.test_doctype import new_doctype
|
||||
|
||||
user = create_user("nested_doc_user@example.com", "Blogger")
|
||||
if not frappe.db.exists("DocType", "Person"):
|
||||
doc = new_doctype("Person",
|
||||
fields=[
|
||||
{
|
||||
"label": "Person Name",
|
||||
"fieldname": "person_name",
|
||||
"fieldtype": "Data"
|
||||
}
|
||||
], unique=0)
|
||||
doc.is_tree = 1
|
||||
doc.insert()
|
||||
|
||||
parent_record = frappe.get_doc(
|
||||
{"doctype": "Person", "person_name": "Parent", "is_group": 1}
|
||||
).insert()
|
||||
|
||||
child_record = frappe.get_doc(
|
||||
{"doctype": "Person", "person_name": "Child", "is_group": 0, "parent_person": parent_record.name}
|
||||
).insert()
|
||||
|
||||
add_user_permissions(get_params(user, "Person", parent_record.name))
|
||||
|
||||
# check if adding perm on a group record, makes child record visible
|
||||
self.assertTrue(has_user_permission(frappe.get_doc("Person", parent_record.name), user.name))
|
||||
self.assertTrue(has_user_permission(frappe.get_doc("Person", child_record.name), user.name))
|
||||
|
||||
frappe.db.set_value("User Permission", {"allow": "Person", "for_value": parent_record.name}, "hide_descendants", 1)
|
||||
frappe.cache().delete_value("user_permissions")
|
||||
|
||||
# check if adding perm on a group record with hide_descendants enabled,
|
||||
# hides child records
|
||||
self.assertTrue(has_user_permission(frappe.get_doc("Person", parent_record.name), user.name))
|
||||
self.assertFalse(has_user_permission(frappe.get_doc("Person", child_record.name), user.name))
|
||||
|
||||
def create_user(email, role="System Manager"):
|
||||
''' create user with role system manager '''
|
||||
if frappe.db.exists('User', email):
|
||||
|
|
@ -119,7 +164,7 @@ def create_user(email, role="System Manager"):
|
|||
user.add_roles(role)
|
||||
return user
|
||||
|
||||
def get_params(user, doctype, docname, is_default=0, applicable=None):
|
||||
def get_params(user, doctype, docname, is_default=0, hide_descendants=0, applicable=None):
|
||||
''' Return param to insert '''
|
||||
param = {
|
||||
"user": user.name,
|
||||
|
|
@ -127,7 +172,8 @@ def get_params(user, doctype, docname, is_default=0, applicable=None):
|
|||
"docname":docname,
|
||||
"is_default": is_default,
|
||||
"apply_to_all_doctypes": 1,
|
||||
"applicable_doctypes": []
|
||||
"applicable_doctypes": [],
|
||||
"hide_descendants": hide_descendants
|
||||
}
|
||||
if applicable:
|
||||
param.update({"apply_to_all_doctypes": 0})
|
||||
|
|
|
|||
|
|
@ -26,11 +26,15 @@ frappe.ui.form.on('User Permission', {
|
|||
() => frappe.set_route('query-report', 'Permitted Documents For User',
|
||||
{ user: frm.doc.user }));
|
||||
frm.trigger('set_applicable_for_constraint');
|
||||
frm.trigger('toggle_hide_descendants');
|
||||
},
|
||||
|
||||
allow: frm => {
|
||||
if(frm.doc.for_value) {
|
||||
frm.set_value('for_value', null);
|
||||
if (frm.doc.allow) {
|
||||
if (frm.doc.for_value) {
|
||||
frm.set_value('for_value', null);
|
||||
}
|
||||
frm.trigger('toggle_hide_descendants');
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -43,6 +47,11 @@ frappe.ui.form.on('User Permission', {
|
|||
if (frm.doc.apply_to_all_doctypes) {
|
||||
frm.set_value('applicable_for', null);
|
||||
}
|
||||
},
|
||||
|
||||
toggle_hide_descendants: frm => {
|
||||
let show = frappe.boot.nested_set_doctypes.includes(frm.doc.allow);
|
||||
frm.toggle_display('hide_descendants', show);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,330 +1,116 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-07-17 14:25:27.881871",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"user",
|
||||
"allow",
|
||||
"column_break_3",
|
||||
"for_value",
|
||||
"is_default",
|
||||
"advanced_control_section",
|
||||
"apply_to_all_doctypes",
|
||||
"applicable_for",
|
||||
"column_break_9",
|
||||
"hide_descendants"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"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": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"search_index": 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": "allow",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Allow",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"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": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "for_value",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "For Value",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "allow",
|
||||
"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,
|
||||
"default": "0",
|
||||
"fieldname": "is_default",
|
||||
"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": "Is Default",
|
||||
"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": "Is Default"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "advanced_control_section",
|
||||
"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": "Advanced Control",
|
||||
"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": "Advanced Control"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "apply_to_all_doctypes",
|
||||
"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": "Apply To All Document Types",
|
||||
"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": "Apply To All Document Types"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:!doc.apply_to_all_doctypes",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "applicable_for",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Applicable For",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"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": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Hide descendant records of <b>For Value</b>.",
|
||||
"fieldname": "hide_descendants",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Hide Descendants"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-04-16 19:17:23.644724",
|
||||
"links": [],
|
||||
"modified": "2021-01-21 18:14:10.839381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User Permission",
|
||||
"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": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "user",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -49,7 +49,8 @@ class UserPermission(Document):
|
|||
'name': ['!=', self.name]
|
||||
}, or_filters={
|
||||
'applicable_for': cstr(self.applicable_for),
|
||||
'apply_to_all_doctypes': 1
|
||||
'apply_to_all_doctypes': 1,
|
||||
'hide_descendants': cstr(self.hide_descendants)
|
||||
}, limit=1)
|
||||
if overlap_exists:
|
||||
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
|
||||
|
|
@ -91,13 +92,13 @@ def get_user_permissions(user=None):
|
|||
|
||||
try:
|
||||
for perm in frappe.get_all('User Permission',
|
||||
fields=['allow', 'for_value', 'applicable_for', 'is_default'],
|
||||
fields=['allow', 'for_value', 'applicable_for', 'is_default', 'hide_descendants'],
|
||||
filters=dict(user=user)):
|
||||
|
||||
meta = frappe.get_meta(perm.allow)
|
||||
add_doc_to_perm(perm, perm.for_value, perm.is_default)
|
||||
|
||||
if meta.is_nested_set():
|
||||
if meta.is_nested_set() and not perm.hide_descendants:
|
||||
decendants = frappe.db.get_descendants(perm.allow, perm.for_value)
|
||||
for doc in decendants:
|
||||
add_doc_to_perm(perm, doc, False)
|
||||
|
|
@ -172,8 +173,8 @@ def check_applicable_doc_perm(user, doctype, docname):
|
|||
"allow": doctype,
|
||||
"for_value":docname,
|
||||
})
|
||||
for d in data:
|
||||
applicable.append(d.applicable_for)
|
||||
for permission in data:
|
||||
applicable.append(permission.applicable_for)
|
||||
return applicable
|
||||
|
||||
|
||||
|
|
@ -194,7 +195,8 @@ def add_user_permissions(data):
|
|||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
|
||||
d = check_applicable_doc_perm(data.user, data.doctype, data.docname)
|
||||
# get all doctypes on whom this permission is applied
|
||||
perm_applied_docs = check_applicable_doc_perm(data.user, data.doctype, data.docname)
|
||||
exists = frappe.db.exists("User Permission", {
|
||||
"user": data.user,
|
||||
"allow": data.doctype,
|
||||
|
|
@ -202,26 +204,27 @@ def add_user_permissions(data):
|
|||
"apply_to_all_doctypes": 1
|
||||
})
|
||||
if data.apply_to_all_doctypes == 1 and not exists:
|
||||
remove_applicable(d, data.user, data.doctype, data.docname)
|
||||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, apply_to_all = 1)
|
||||
remove_applicable(perm_applied_docs, data.user, data.doctype, data.docname)
|
||||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.hide_descendants, apply_to_all=1)
|
||||
return 1
|
||||
elif len(data.applicable_doctypes) > 0 and data.apply_to_all_doctypes != 1:
|
||||
remove_apply_to_all(data.user, data.doctype, data.docname)
|
||||
update_applicable(d, data.applicable_doctypes, data.user, data.doctype, data.docname)
|
||||
update_applicable(perm_applied_docs, data.applicable_doctypes, data.user, data.doctype, data.docname)
|
||||
for applicable in data.applicable_doctypes :
|
||||
if applicable not in d:
|
||||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable)
|
||||
if applicable not in perm_applied_docs:
|
||||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.hide_descendants, applicable=applicable)
|
||||
elif exists:
|
||||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, applicable = applicable)
|
||||
insert_user_perm(data.user, data.doctype, data.docname, data.is_default, data.hide_descendants, applicable=applicable)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def insert_user_perm(user, doctype, docname, is_default=0, apply_to_all=None, applicable=None):
|
||||
def insert_user_perm(user, doctype, docname, is_default=0, hide_descendants=0, apply_to_all=None, applicable=None):
|
||||
user_perm = frappe.new_doc("User Permission")
|
||||
user_perm.user = user
|
||||
user_perm.allow = doctype
|
||||
user_perm.for_value = docname
|
||||
user_perm.is_default = is_default
|
||||
user_perm.hide_descendants = hide_descendants
|
||||
if applicable:
|
||||
user_perm.applicable_for = applicable
|
||||
user_perm.apply_to_all_doctypes = 0
|
||||
|
|
@ -229,8 +232,8 @@ def insert_user_perm(user, doctype, docname, is_default=0, apply_to_all=None, ap
|
|||
user_perm.apply_to_all_doctypes = 1
|
||||
user_perm.insert()
|
||||
|
||||
def remove_applicable(d, user, doctype, docname):
|
||||
for applicable_for in d:
|
||||
def remove_applicable(perm_applied_docs, user, doctype, docname):
|
||||
for applicable_for in perm_applied_docs:
|
||||
frappe.db.sql("""DELETE FROM `tabUser Permission`
|
||||
WHERE `user`=%s
|
||||
AND `applicable_for`=%s
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ frappe.listview_settings['User Permission'] = {
|
|||
dialog.set_df_property("is_default", "hidden", 1);
|
||||
dialog.set_df_property("apply_to_all_doctypes", "hidden", 1);
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
dialog.set_df_property("hide_descendants", "hidden", 1);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -54,6 +55,10 @@ frappe.listview_settings['User Permission'] = {
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
hide_border: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'is_default',
|
||||
label: __('Is Default'),
|
||||
|
|
@ -74,6 +79,19 @@ frappe.listview_settings['User Permission'] = {
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break"
|
||||
},
|
||||
{
|
||||
fieldname: 'hide_descendants',
|
||||
label: __('Hide Descendants'),
|
||||
fieldtype: 'Check',
|
||||
hidden: 1
|
||||
},
|
||||
{
|
||||
fieldtype: "Section Break",
|
||||
hide_border: 1
|
||||
},
|
||||
{
|
||||
label: __("Applicable Document Types"),
|
||||
fieldname: "applicable_doctypes",
|
||||
|
|
@ -214,6 +232,9 @@ frappe.listview_settings['User Permission'] = {
|
|||
dialog.set_df_property("is_default", "hidden", 0);
|
||||
dialog.set_df_property("apply_to_all_doctypes", "hidden", 0);
|
||||
dialog.set_value("apply_to_all_doctypes", "checked", 1);
|
||||
let show = frappe.boot.nested_set_doctypes.includes(dialog.get_value("doctype"));
|
||||
dialog.set_df_property("hide_descendants", "hidden", !show);
|
||||
dialog.refresh();
|
||||
},
|
||||
|
||||
on_docname_change: function(dialog, options, applicable) {
|
||||
|
|
@ -233,6 +254,7 @@ frappe.listview_settings['User Permission'] = {
|
|||
dialog.set_df_property("applicable_doctypes", "options", options);
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
}
|
||||
dialog.refresh();
|
||||
},
|
||||
|
||||
on_apply_to_all_doctypes_change: function(dialog, options) {
|
||||
|
|
@ -243,5 +265,6 @@ frappe.listview_settings['User Permission'] = {
|
|||
dialog.set_df_property("applicable_doctypes", "options", options);
|
||||
dialog.set_df_property("applicable_doctypes", "hidden", 1);
|
||||
}
|
||||
dialog.refresh_sections();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<td class="danger">{{ item[1] }}</td>
|
||||
<td class="success">{{ item[2] }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
@ -93,4 +93,4 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
frappe.pages["background_jobs"].on_page_load = (wrapper) => {
|
||||
background_job = new BackgroundJobs(wrapper);
|
||||
const background_job = new BackgroundJobs(wrapper);
|
||||
|
||||
$(wrapper).bind('show', () => {
|
||||
background_job.show();
|
||||
|
|
@ -20,10 +20,12 @@ class BackgroundJobs {
|
|||
this.show_failed = false;
|
||||
|
||||
this.show_failed_button = this.page.add_inner_button(__("Show Failed Jobs"), () => {
|
||||
this.show_failed = !this.show_failed
|
||||
this.show_failed_button && this.show_failed_button.text(
|
||||
this.show_failed ? __("Hide Failed Jobs") : __("Show Failed Jobs")
|
||||
)
|
||||
this.show_failed = !this.show_failed;
|
||||
if (this.show_failed_button) {
|
||||
this.show_failed_button.text(
|
||||
this.show_failed ? __("Hide Failed Jobs") : __("Show Failed Jobs")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$(frappe.render_template('background_jobs_outer')).appendTo(this.page.body);
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ class Dashboard {
|
|||
frappe.db.get_list('Dashboard').then(dashboards => {
|
||||
dashboards.map(dashboard => {
|
||||
let name = dashboard.name;
|
||||
if(name != this.dashboard_name){
|
||||
if (name != this.dashboard_name) {
|
||||
this.page.add_menu_item(name, () => frappe.set_route("dashboard-view", name), 1);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
frappe.pages['desktop'].on_page_load = function() {
|
||||
frappe.utils.set_title(__("Home"));
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2019-01-29 13:11:48.872579",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"icon": "icon-th",
|
||||
"idx": 0,
|
||||
"modified": "2019-01-29 13:11:48.872579",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "desktop",
|
||||
"owner": "Administrator",
|
||||
"page_name": "desktop",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0,
|
||||
"title": "Desktop"
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
}
|
||||
|
||||
reset_std_permissions(data) {
|
||||
let doctype = this.get_doctype()
|
||||
let doctype = this.get_doctype();
|
||||
let d = frappe.confirm(__("Reset Permissions for {0}?", [doctype]), () => {
|
||||
return frappe.call({
|
||||
module: "frappe.core",
|
||||
|
|
@ -117,7 +117,7 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
let rights = this.rights
|
||||
.filter((r) => d[r])
|
||||
.map((r) => {
|
||||
return __(toTitle(frappe.unscrub(r)))
|
||||
return __(toTitle(frappe.unscrub(r)));
|
||||
});
|
||||
|
||||
d.rights = rights.join(", ");
|
||||
|
|
@ -153,16 +153,14 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
this.page.clear_primary_action();
|
||||
|
||||
if (!this.doctype_select) {
|
||||
this.set_empty_message(__("Loading"))
|
||||
return
|
||||
return this.set_empty_message(__("Loading"));
|
||||
}
|
||||
|
||||
let doctype = this.get_doctype();
|
||||
let role = this.get_role();
|
||||
|
||||
if (!doctype && !role) {
|
||||
this.set_empty_message(__("Select Document Type or Role to start."))
|
||||
return;
|
||||
return this.set_empty_message(__("Select Document Type or Role to start."));
|
||||
}
|
||||
|
||||
// get permissions
|
||||
|
|
@ -202,7 +200,7 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
[__("Level"), 40],
|
||||
[__("Permissions"), 350],
|
||||
["", 40]
|
||||
]
|
||||
];
|
||||
|
||||
table_columns.forEach((col) => {
|
||||
$("<th>")
|
||||
|
|
@ -292,8 +290,8 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
}
|
||||
|
||||
get rights() {
|
||||
return ["read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
"print", "email", "report", "import", "export", "set_user_permissions", "share"]
|
||||
return ["select", "read", "write", "create", "delete", "submit", "cancel", "amend",
|
||||
"print", "email", "report", "import", "export", "set_user_permissions", "share"];
|
||||
}
|
||||
|
||||
set_show_users(cell, role) {
|
||||
|
|
@ -436,7 +434,7 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
d.show();
|
||||
},
|
||||
"small-add"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
make_reset_button() {
|
||||
|
|
@ -459,4 +457,4 @@ frappe.PermissionEngine = class PermissionEngine {
|
|||
return frappe.get_children("DocType", doctype, "fields",
|
||||
{ fieldtype: "Link", options: ["not in", ["User", '[Select]']] });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -77,6 +77,18 @@ def add(parent, role, permlevel):
|
|||
|
||||
@frappe.whitelist()
|
||||
def update(doctype, role, permlevel, ptype, value=None):
|
||||
"""Update role permission params
|
||||
|
||||
Args:
|
||||
doctype (str): Name of the DocType to update params for
|
||||
role (str): Role to be updated for, eg "Website Manager".
|
||||
permlevel (int): perm level the provided rule applies to
|
||||
ptype (str): permission type, example "read", "delete", etc.
|
||||
value (None, optional): value for ptype, None indicates False
|
||||
|
||||
Returns:
|
||||
str: Refresh flag is permission is updated successfully
|
||||
"""
|
||||
frappe.only_for("System Manager")
|
||||
out = update_permission_property(doctype, role, permlevel, ptype, value)
|
||||
return 'refresh' if out else None
|
||||
|
|
@ -92,7 +104,7 @@ def remove(doctype, role, permlevel):
|
|||
if not frappe.get_all('Custom DocPerm', dict(parent=doctype)):
|
||||
frappe.throw(_('There must be atleast one permission rule.'), title=_('Cannot Remove'))
|
||||
|
||||
validate_permissions_for_doctype(doctype, for_remove=True)
|
||||
validate_permissions_for_doctype(doctype, for_remove=True, alert=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset(doctype):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ frappe.pages['recorder'].on_page_load = function(wrapper) {
|
|||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: 'Recorder',
|
||||
single_column: true
|
||||
single_column: true,
|
||||
card_layout: true
|
||||
});
|
||||
|
||||
frappe.recorder = new Recorder(wrapper);
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
frappe.pages['space'].on_page_load = function (wrapper) {
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
name: 'space',
|
||||
title: __("Workspace"),
|
||||
});
|
||||
|
||||
frappe.workspace = new frappe.views.Workspace(wrapper);
|
||||
$(wrapper).bind('show', function () {
|
||||
frappe.workspace.show();
|
||||
});
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"content": null,
|
||||
"creation": "2020-02-27 15:07:57.124916",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"icon": "icon-th",
|
||||
"idx": 0,
|
||||
"modified": "2020-12-16 14:22:05.591912",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "space",
|
||||
"owner": "Administrator",
|
||||
"page_name": "space",
|
||||
"roles": [
|
||||
{
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"system_page": 0
|
||||
}
|
||||
211
frappe/core/workspace/build/build.json
Normal file
211
frappe/core/workspace/build/build.json
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
{
|
||||
"cards_label": "Elements",
|
||||
"category": "Modules",
|
||||
"charts": [],
|
||||
"creation": "2021-01-02 10:51:16.579957",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"icon": "tool",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Build",
|
||||
"links": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Modules",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Module Def",
|
||||
"link_to": "Module Def",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Workspace",
|
||||
"link_to": "Workspace",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Module Onboarding",
|
||||
"link_to": "Module Onboarding",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Block Module",
|
||||
"link_to": "Block Module",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Models",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "DocType",
|
||||
"link_to": "DocType",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Workflow",
|
||||
"link_to": "Workflow",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Views",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Report",
|
||||
"link_to": "Report",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Print Format",
|
||||
"link_to": "Print Format",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Workspace",
|
||||
"link_to": "Workspace",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Dashboard",
|
||||
"link_to": "Dashboard",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Scripting",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Server Script",
|
||||
"link_to": "Server Script",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Custom Script",
|
||||
"link_to": "Custom Script",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Scheduled Job Type",
|
||||
"link_to": "Scheduled Job Type",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"only_for": "",
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-01-02 14:03:15.029699",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Build",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"doc_view": "",
|
||||
"label": "DocType",
|
||||
"link_to": "DocType",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"label": "Workspace",
|
||||
"link_to": "Workspace",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"doc_view": "",
|
||||
"label": "Report",
|
||||
"link_to": "Report",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ frappe.ui.form.on('Custom Script', {
|
|||
}
|
||||
],
|
||||
primary_action: ({ cdt }) => {
|
||||
cdt = d.get_field('cdt').value;
|
||||
frm.events.add_script_for_doctype(frm, cdt);
|
||||
d.hide();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,35 @@
|
|||
frappe.provide("frappe.customize_form");
|
||||
|
||||
frappe.ui.form.on("Customize Form", {
|
||||
setup: function(frm) {
|
||||
// save the last setting if refreshing
|
||||
window.addEventListener("beforeunload", () => {
|
||||
if (frm.doc.doc_type && frm.doc.doc_type != "undefined") {
|
||||
localStorage["customize_doctype"] = frm.doc.doc_type;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.set_query("doc_type", function() {
|
||||
return {
|
||||
translate_values: false,
|
||||
filters: [
|
||||
['DocType', 'issingle', '=', 0],
|
||||
['DocType', 'custom', '=', 0],
|
||||
['DocType', 'name', 'not in', frappe.model.core_doctypes_list],
|
||||
['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains]
|
||||
["DocType", "issingle", "=", 0],
|
||||
["DocType", "custom", "=", 0],
|
||||
[
|
||||
"DocType",
|
||||
"name",
|
||||
"not in",
|
||||
frappe.model.core_doctypes_list
|
||||
],
|
||||
[
|
||||
"DocType",
|
||||
"restrict_to_domain",
|
||||
"in",
|
||||
frappe.boot.active_domains
|
||||
]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
|
@ -21,15 +40,15 @@ frappe.ui.form.on("Customize Form", {
|
|||
frm.set_query("default_print_format", function() {
|
||||
return {
|
||||
filters: {
|
||||
'print_format_type': ['!=', 'JS'],
|
||||
'doc_type': ['=', frm.doc.doc_type]
|
||||
print_format_type: ["!=", "JS"],
|
||||
doc_type: ["=", frm.doc.doc_type]
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$(frm.wrapper).on("grid-row-render", function(e, grid_row) {
|
||||
if (grid_row.doc && grid_row.doc.fieldtype=="Section Break") {
|
||||
$(grid_row.row).css({"font-weight": "bold"});
|
||||
if (grid_row.doc && grid_row.doc.fieldtype == "Section Break") {
|
||||
$(grid_row.row).css({ "font-weight": "bold" });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -40,12 +59,6 @@ frappe.ui.form.on("Customize Form", {
|
|||
$(frm.wrapper).on("grid-move-row", function(e, frm) {
|
||||
frm.trigger("setup_sortable");
|
||||
});
|
||||
|
||||
if (localStorage['customize_doctype']) {
|
||||
// set default value from customize form
|
||||
frm.set_value('doc_type', localStorage['customize_doctype']);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
doc_type: function(frm) {
|
||||
|
|
@ -59,7 +72,6 @@ frappe.ui.form.on("Customize Form", {
|
|||
if (r._server_messages && r._server_messages.length) {
|
||||
frm.set_value("doc_type", "");
|
||||
} else {
|
||||
localStorage['customize_doctype'] = frm.doc.doc_type;
|
||||
frm.refresh();
|
||||
frm.trigger("setup_sortable");
|
||||
}
|
||||
|
|
@ -72,9 +84,11 @@ frappe.ui.form.on("Customize Form", {
|
|||
},
|
||||
|
||||
setup_sortable: function(frm) {
|
||||
frm.page.body.find('.highlight').removeClass('highlight');
|
||||
frm.page.body.find(".highlight").removeClass("highlight");
|
||||
frm.doc.fields.forEach(function(f, i) {
|
||||
var data_row = frm.page.body.find('[data-fieldname="fields"] [data-idx="'+ f.idx +'"] .data-row');
|
||||
var data_row = frm.page.body.find(
|
||||
'[data-fieldname="fields"] [data-idx="' + f.idx + '"] .data-row'
|
||||
);
|
||||
|
||||
if (f.is_custom_field) {
|
||||
data_row.addClass("highlight");
|
||||
|
|
@ -82,9 +96,13 @@ frappe.ui.form.on("Customize Form", {
|
|||
f._sortable = false;
|
||||
}
|
||||
if (f.fieldtype == "Table") {
|
||||
frm.add_custom_button(f.options, function() {
|
||||
frm.set_value('doc_type', f.options);
|
||||
}, __('Customize Child Table'));
|
||||
frm.add_custom_button(
|
||||
f.options,
|
||||
function() {
|
||||
frm.set_value("doc_type", f.options);
|
||||
},
|
||||
__("Customize Child Table")
|
||||
);
|
||||
}
|
||||
});
|
||||
frm.fields_dict.fields.grid.refresh();
|
||||
|
|
@ -97,36 +115,91 @@ frappe.ui.form.on("Customize Form", {
|
|||
if (frm.doc.doc_type) {
|
||||
frappe.customize_form.set_primary_action(frm);
|
||||
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.doc_type]), function() {
|
||||
frappe.set_route('List', frm.doc.doc_type);
|
||||
}, __('Actions'));
|
||||
frm.add_custom_button(
|
||||
__("Go to {0} List", [frm.doc.doc_type]),
|
||||
function() {
|
||||
frappe.set_route("List", frm.doc.doc_type);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(__('Reload'), function() {
|
||||
frm.script_manager.trigger("doc_type");
|
||||
}, __('Actions'));
|
||||
frm.add_custom_button(
|
||||
__("Reload"),
|
||||
function() {
|
||||
frm.script_manager.trigger("doc_type");
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(__('Reset to defaults'), function() {
|
||||
frappe.customize_form.confirm(__('Remove all customizations?'), frm);
|
||||
}, __('Actions'));
|
||||
frm.add_custom_button(
|
||||
__("Reset to defaults"),
|
||||
function() {
|
||||
frappe.customize_form.confirm(
|
||||
__("Remove all customizations?"),
|
||||
frm
|
||||
);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(__('Set Permissions'), function() {
|
||||
frappe.set_route('permission-manager', frm.doc.doc_type);
|
||||
}, __('Actions'));
|
||||
frm.add_custom_button(
|
||||
__("Set Permissions"),
|
||||
function() {
|
||||
frappe.set_route("permission-manager", frm.doc.doc_type);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
}
|
||||
|
||||
if (frappe.boot.developer_mode) {
|
||||
frm.add_custom_button(__('Export Customizations'), function() {
|
||||
frm.events.setup_export(frm);
|
||||
frm.events.setup_sort_order(frm);
|
||||
frm.events.set_default_doc_type(frm);
|
||||
},
|
||||
|
||||
set_default_doc_type(frm) {
|
||||
let doc_type;
|
||||
if (frappe.route_options && frappe.route_options.doc_type) {
|
||||
doc_type = frappe.route_options.doc_type;
|
||||
frappe.route_options = null;
|
||||
localStorage.removeItem("customize_doctype");
|
||||
}
|
||||
if (!doc_type) {
|
||||
doc_type = localStorage.getItem("customize_doctype");
|
||||
}
|
||||
if (doc_type) {
|
||||
setTimeout(() => frm.set_value("doc_type", doc_type), 1000);
|
||||
}
|
||||
},
|
||||
|
||||
setup_export(frm) {
|
||||
if (frappe.boot.developer_mode) {
|
||||
frm.add_custom_button(
|
||||
__("Export Customizations"),
|
||||
function() {
|
||||
frappe.prompt(
|
||||
[
|
||||
{fieldtype:'Link', fieldname:'module', options:'Module Def',
|
||||
label: __('Module to Export')},
|
||||
{fieldtype:'Check', fieldname:'sync_on_migrate',
|
||||
label: __('Sync on Migrate'), 'default': 1},
|
||||
{fieldtype:'Check', fieldname:'with_permissions',
|
||||
label: __('Export Custom Permissions'), 'default': 1},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "module",
|
||||
options: "Module Def",
|
||||
label: __("Module to Export")
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
fieldname: "sync_on_migrate",
|
||||
label: __("Sync on Migrate"),
|
||||
default: 1
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
fieldname: "with_permissions",
|
||||
label: __("Export Custom Permissions"),
|
||||
default: 1
|
||||
}
|
||||
],
|
||||
function(data) {
|
||||
frappe.call({
|
||||
method: 'frappe.modules.utils.export_customizations',
|
||||
method: "frappe.modules.utils.export_customizations",
|
||||
args: {
|
||||
doctype: frm.doc.doc_type,
|
||||
module: data.module,
|
||||
|
|
@ -135,27 +208,25 @@ frappe.ui.form.on("Customize Form", {
|
|||
}
|
||||
});
|
||||
},
|
||||
__("Select Module"));
|
||||
}, __('Actions'));
|
||||
}
|
||||
__("Select Module")
|
||||
);
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
setup_sort_order(frm) {
|
||||
// sort order select
|
||||
if (frm.doc.doc_type) {
|
||||
var fields = $.map(frm.doc.fields,
|
||||
function(df) {
|
||||
return frappe.model.is_value_type(df.fieldtype) ? df.fieldname : null;
|
||||
});
|
||||
var fields = $.map(frm.doc.fields, function(df) {
|
||||
return frappe.model.is_value_type(df.fieldtype)
|
||||
? df.fieldname
|
||||
: null;
|
||||
});
|
||||
fields = ["", "name", "modified"].concat(fields);
|
||||
frm.set_df_property("sort_field", "options", fields);
|
||||
}
|
||||
|
||||
if (frappe.route_options && frappe.route_options.doc_type) {
|
||||
setTimeout(function() {
|
||||
frm.set_value("doc_type", frappe.route_options.doc_type);
|
||||
frappe.route_options = null;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -455,11 +455,15 @@ class CustomizeForm(Document):
|
|||
self.fetch_to_customize()
|
||||
|
||||
def reset_customization(doctype):
|
||||
frappe.db.sql("""
|
||||
DELETE FROM `tabProperty Setter` WHERE doc_type=%s
|
||||
and `field_name`!='naming_series'
|
||||
and `property`!='options'
|
||||
""", doctype)
|
||||
setters = frappe.get_all("Property Setter", filters={
|
||||
'doc_type': doctype,
|
||||
'field_name': ['!=', 'naming_series'],
|
||||
'property': ['!=', 'options']
|
||||
}, pluck='name')
|
||||
|
||||
for setter in setters:
|
||||
frappe.delete_doc("Property Setter", setter)
|
||||
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
|
||||
doctype_properties = {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,11 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
from frappe.desk.utils import get_doctype_route
|
||||
from frappe.desk.utils import slug
|
||||
|
||||
class DocTypeLayout(Document):
|
||||
def validate(self):
|
||||
if not self.route:
|
||||
self.route = get_doctype_route(self.name)
|
||||
self.route = slug(self.name)
|
||||
|
|
|
|||
8
frappe/custom/doctype/test_rename_new/test_rename_new.js
Normal file
8
frappe/custom/doctype/test_rename_new/test_rename_new.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2021, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Test rename new', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
42
frappe/custom/doctype/test_rename_new/test_rename_new.json
Normal file
42
frappe/custom/doctype/test_rename_new/test_rename_new.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2021-01-13 12:47:03.572640",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"random"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "random",
|
||||
"fieldtype": "Data",
|
||||
"label": "random"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-13 12:47:03.572640",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Test rename new",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"route": "test-rename",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue