Merge branch 'develop' into newsletter-modifications
This commit is contained in:
commit
5e30d54bfb
779 changed files with 32637 additions and 41668 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,
|
||||
|
|
@ -146,6 +147,7 @@
|
|||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"qz": true
|
||||
"qz": true,
|
||||
"localforage": 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/
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ pull_request_rules:
|
|||
- name: Automatic squash on CI success and review
|
||||
conditions:
|
||||
- status-success=Sider
|
||||
- status-success=Semantic Pull Request
|
||||
- status-success=Travis CI - Pull Request
|
||||
- status-success=security/snyk (frappe)
|
||||
- label!=dont-merge
|
||||
|
|
|
|||
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
|
||||
}
|
||||
}
|
||||
17
.travis.yml
17
.travis.yml
|
|
@ -43,7 +43,6 @@ matrix:
|
|||
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,12 +2,12 @@ context('API Resources', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
it('Creates two Comments', () => {
|
||||
cy.insert_doc('Comment', {comment_type: 'Comment', content: "hello"});
|
||||
cy.insert_doc('Comment', {comment_type: 'Comment', content: "world"});
|
||||
cy.insert_doc('Comment', { comment_type: 'Comment', content: "hello" });
|
||||
cy.insert_doc('Comment', { comment_type: 'Comment', content: "world" });
|
||||
});
|
||||
|
||||
it('Lists the Comments', () => {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ context('Awesome Bar', () => {
|
|||
before(() => {
|
||||
cy.visit('/login');
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('.navbar-header .navbar-home').click();
|
||||
cy.get('.navbar .navbar-home').click();
|
||||
});
|
||||
|
||||
it('navigates to doctype list', () => {
|
||||
|
|
@ -14,16 +14,16 @@ context('Awesome Bar', () => {
|
|||
cy.get('#navbar-search + ul').should('be.visible');
|
||||
cy.get('#navbar-search').type('{downarrow}{enter}', { delay: 100 });
|
||||
|
||||
cy.get('h1').should('contain', 'To Do');
|
||||
cy.get('.title-text').should('contain', 'To Do');
|
||||
|
||||
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||
cy.location('pathname').should('eq', '/app/todo');
|
||||
});
|
||||
|
||||
it('find text in doctype list', () => {
|
||||
cy.get('#navbar-search')
|
||||
.type('test in todo{downarrow}{enter}', { delay: 200 });
|
||||
|
||||
cy.get('h1').should('contain', 'To Do');
|
||||
cy.get('.title-text').should('contain', 'To Do');
|
||||
|
||||
cy.get('[data-original-title="Name"] > .input-with-feedback')
|
||||
.should('have.value', '%test%');
|
||||
|
|
@ -33,7 +33,7 @@ context('Awesome Bar', () => {
|
|||
cy.get('#navbar-search')
|
||||
.type('new blog post{downarrow}{enter}', { delay: 200 });
|
||||
|
||||
cy.get('.title-text:visible').should('have.text', 'New Blog Post 1');
|
||||
cy.get('.title-text:visible').should('have.text', 'New Blog Post');
|
||||
});
|
||||
|
||||
it('calculates math expressions', () => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Barcode', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
function get_dialog_with_barcode() {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
context('Control Duration', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
function get_dialog_with_duration(hide_days=0, hide_seconds=0) {
|
||||
function get_dialog_with_duration(hide_days = 0, hide_seconds = 0) {
|
||||
return cy.dialog({
|
||||
title: 'Duration',
|
||||
fields: [{
|
||||
|
|
@ -22,11 +22,11 @@ context('Control Duration', () => {
|
|||
.first()
|
||||
.click();
|
||||
cy.get('.duration-input[data-duration=days]')
|
||||
.type(45, {force: true})
|
||||
.blur({force: true});
|
||||
.type(45, { force: true })
|
||||
.blur({ force: true });
|
||||
cy.get('.duration-input[data-duration=minutes]')
|
||||
.type(30)
|
||||
.blur({force: true});
|
||||
.blur({ force: true });
|
||||
cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m');
|
||||
cy.get('.frappe-control[data-fieldname=duration] input').first().blur();
|
||||
cy.get('.duration-picker').should('not.be.visible');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
context('Control Link', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/desk#workspace/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');
|
||||
|
|
@ -77,7 +74,7 @@ context('Control Link', () => {
|
|||
cy.get('.frappe-control[data-fieldname=link] .link-btn')
|
||||
.should('be.visible')
|
||||
.click();
|
||||
cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`);
|
||||
cy.location('pathname').should('eq', `/app/todo/${todos[0]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('Control Rating', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
function get_dialog_with_rating() {
|
||||
|
|
@ -18,7 +18,7 @@ context('Control Rating', () => {
|
|||
get_dialog_with_rating().as('dialog');
|
||||
|
||||
cy.get('div.rating')
|
||||
.children('i.fa')
|
||||
.children('svg')
|
||||
.first()
|
||||
.click()
|
||||
.should('have.class', 'star-click');
|
||||
|
|
@ -33,11 +33,11 @@ context('Control Rating', () => {
|
|||
get_dialog_with_rating();
|
||||
|
||||
cy.get('div.rating')
|
||||
.children('i.fa')
|
||||
.children('svg')
|
||||
.first()
|
||||
.invoke('trigger', 'mouseenter')
|
||||
.should('have.class', 'star-hover')
|
||||
.invoke('trigger', 'mouseleave')
|
||||
.should('not.have.class', 'star-hover');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name;
|
|||
context('Control Date, Time and DateTime', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/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,7 +1,7 @@
|
|||
context('Depends On', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall('frappe.tests.ui_test_helpers.create_child_doctype', {
|
||||
name: 'Child Test Depends On',
|
||||
|
|
@ -64,7 +64,7 @@ context('Depends On', () => {
|
|||
cy.fill_field('test_field', 'Some Value');
|
||||
cy.get('button.primary-action').contains('Save').click();
|
||||
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible');
|
||||
cy.get('body').click();
|
||||
cy.hide_dialog();
|
||||
cy.fill_field('test_field', 'Random value');
|
||||
cy.get('button.primary-action').contains('Save').click();
|
||||
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible');
|
||||
|
|
@ -92,7 +92,7 @@ context('Depends On', () => {
|
|||
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('.octicon-triangle-up').click();
|
||||
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');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
context('FileUploader', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app');
|
||||
});
|
||||
|
||||
function open_upload_dialog() {
|
||||
|
|
@ -19,44 +19,36 @@ context('FileUploader', () => {
|
|||
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-info').should('contain', 'example.json');
|
||||
cy.server();
|
||||
cy.route('POST', '/api/method/upload_file').as('upload_file');
|
||||
cy.get_open_dialog().find('.btn-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('a:contains("uploaded file")').click();
|
||||
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');
|
||||
});
|
||||
|
||||
it('should accept web links', () => {
|
||||
open_upload_dialog();
|
||||
|
||||
cy.get_open_dialog().find('a:contains("web link")').click();
|
||||
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,56 +1,50 @@
|
|||
context('Form', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
|
||||
});
|
||||
});
|
||||
it('create a new form', () => {
|
||||
cy.visit('/desk#Form/ToDo/New ToDo 1');
|
||||
cy.visit('/app/todo/new');
|
||||
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.visit('/desk#List/ToDo');
|
||||
cy.location('hash').should('eq', '#List/ToDo/List');
|
||||
cy.get('h1').should('be.visible').and('contain', 'To Do');
|
||||
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');
|
||||
});
|
||||
it('navigates between documents with child table list filters applied', () => {
|
||||
cy.visit('/desk#List/Contact');
|
||||
cy.location('hash').should('eq', '#List/Contact/List');
|
||||
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
|
||||
cy.get('.fieldname-select-area').should('exist');
|
||||
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true });
|
||||
cy.visit('/app/contact');
|
||||
cy.add_filter();
|
||||
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true });
|
||||
cy.get('.filter-box .btn:contains("Apply")').click({ force: true });
|
||||
cy.visit('/desk#Form/Contact/Test Form Contact 3');
|
||||
cy.get('.filter-popover .apply-filters').click({ force: true });
|
||||
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('.page-title .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('/desk#Form/Contact/New Contact 1');
|
||||
cy.visit('/app/contact/new');
|
||||
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('.grid-body .rows [data-fieldname="email_id"]').click();
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
context('Grid Pagination', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/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");
|
||||
});
|
||||
});
|
||||
it('creates pages for child table', () => {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.visit('/app/contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.current-page-number').should('contain', '1');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
|
||||
});
|
||||
it('goes to the next and previous page', () => {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
cy.visit('/app/contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('.next-page').click();
|
||||
cy.get('@table').find('.current-page-number').should('contain', '2');
|
||||
|
|
@ -27,21 +27,21 @@ context('Grid Pagination', () => {
|
|||
cy.get('@table').find('.current-page-number').should('contain', '1');
|
||||
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
|
||||
});
|
||||
it('adds and deletes rows and changes page', ()=> {
|
||||
cy.visit('/desk#Form/Contact/Test Contact');
|
||||
it('adds and deletes rows and changes page', () => {
|
||||
cy.visit('/app/contact/Test Contact');
|
||||
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
cy.get('@table').find('button.grid-add-row').click();
|
||||
cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
|
||||
cy.get('@table').find('.current-page-number').should('contain', '21');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '21');
|
||||
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({force: true});
|
||||
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true });
|
||||
cy.get('@table').find('button.grid-remove-rows').click();
|
||||
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000);
|
||||
cy.get('@table').find('.current-page-number').should('contain', '20');
|
||||
cy.get('@table').find('.total-page-number').should('contain', '20');
|
||||
});
|
||||
// it('deletes all rows', ()=> {
|
||||
// cy.visit('/desk#Form/Contact/Test Contact');
|
||||
// cy.visit('/app/contact/Test Contact');
|
||||
// cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
|
||||
// cy.get('@table').find('.grid-heading-row .grid-row-check').click({force: true});
|
||||
// cy.get('@table').find('button.grid-remove-all-rows').click();
|
||||
|
|
|
|||
|
|
@ -1,30 +1,31 @@
|
|||
context('List View', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
return cy.window().its('frappe').then(frappe => {
|
||||
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
|
||||
});
|
||||
});
|
||||
it('enables "Actions" button', () => {
|
||||
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Print', 'Delete'];
|
||||
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete'];
|
||||
cy.go_to_list('ToDo');
|
||||
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true });
|
||||
cy.get('.btn.btn-primary.btn-sm.dropdown-toggle').contains('Actions').should('be.visible').click();
|
||||
cy.get('.dropdown-menu li:visible').should('have.length', 7).each((el, index) => {
|
||||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
|
||||
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'
|
||||
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'
|
||||
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,36 +1,36 @@
|
|||
context('List View Settings', () => {
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
it('Default settings', () => {
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.visit('/app/List/DocType/List');
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('.sidebar-stat').should('contain', "Tags");
|
||||
cy.get('.list-stats').should('contain', "Tags");
|
||||
});
|
||||
it('disable count and sidebar stats then verify', () => {
|
||||
cy.wait(300);
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.visit('/app/List/DocType/List');
|
||||
cy.wait(300);
|
||||
cy.get('.list-count').should('contain', "20 of");
|
||||
cy.get('button').contains('Menu').click();
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
cy.get('.menu-btn-group button').click();
|
||||
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});
|
||||
cy.get('input[data-fieldname="disable_count"]').check({ force: true });
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({ force: true });
|
||||
cy.get('button').filter(':visible').contains('Save').click();
|
||||
|
||||
cy.reload();
|
||||
cy.reload({ force: true });
|
||||
|
||||
cy.get('.list-count').should('be.empty');
|
||||
cy.get('.list-sidebar .sidebar-stat').should('not.exist');
|
||||
cy.get('.list-sidebar .list-tags').should('not.exist');
|
||||
|
||||
cy.get('button').contains('Menu').click({force: true});
|
||||
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
|
||||
cy.get('.modal-dialog').should('contain', 'Settings');
|
||||
cy.get('input[data-fieldname="disable_count"]').uncheck({force: true});
|
||||
cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({force: true});
|
||||
cy.get('.menu-btn-group button').click({ force: true });
|
||||
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', () => {
|
||||
|
|
@ -11,13 +11,13 @@ context('Login', () => {
|
|||
|
||||
it('validates password', () => {
|
||||
cy.get('#login_email').type('Administrator');
|
||||
cy.get('.btn-login').click();
|
||||
cy.get('.btn-login:visible').click();
|
||||
cy.location('pathname').should('eq', '/login');
|
||||
});
|
||||
|
||||
it('validates email', () => {
|
||||
cy.get('#login_password').type('qwe');
|
||||
cy.get('.btn-login').click();
|
||||
cy.get('.btn-login:visible').click();
|
||||
cy.location('pathname').should('eq', '/login');
|
||||
});
|
||||
|
||||
|
|
@ -25,8 +25,8 @@ context('Login', () => {
|
|||
cy.get('#login_email').type('Administrator');
|
||||
cy.get('#login_password').type('qwer');
|
||||
|
||||
cy.get('.btn-login').click();
|
||||
cy.get('.page-card-head').contains('Invalid Login. Try again.');
|
||||
cy.get('.btn-login:visible').click();
|
||||
cy.get('.btn-login:visible').contains('Invalid Login. Try again.');
|
||||
cy.location('pathname').should('eq', '/login');
|
||||
});
|
||||
|
||||
|
|
@ -34,8 +34,8 @@ context('Login', () => {
|
|||
cy.get('#login_email').type('Administrator');
|
||||
cy.get('#login_password').type(Cypress.config('adminPassword'));
|
||||
|
||||
cy.get('.btn-login').click();
|
||||
cy.location('pathname').should('eq', '/desk');
|
||||
cy.get('.btn-login:visible').click();
|
||||
cy.location('pathname').should('eq', '/app');
|
||||
cy.window().its('frappe.session.user').should('eq', 'Administrator');
|
||||
});
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ context('Login', () => {
|
|||
cy.get('#login_email').type('Administrator');
|
||||
cy.get('#login_password').type(Cypress.config('adminPassword'));
|
||||
|
||||
cy.get('.btn-login').click();
|
||||
cy.get('.btn-login:visible').click();
|
||||
|
||||
// verify redirected location and url params after login
|
||||
cy.url().should('include', '/me?' + payload.toString().replace('+', '%20'));
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
context('Query Report', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
});
|
||||
|
||||
it('add custom column in report', () => {
|
||||
cy.visit('/desk#query-report/Permitted Documents For User');
|
||||
cy.visit('/app/query-report/Permitted Documents For User');
|
||||
|
||||
cy.get('div[class="page-form flex"]', {timeout: 60000}).should('have.length', 1).then(()=>{
|
||||
cy.get('.page-form.flex', { timeout: 60000 }).should('have.length', 1).then(() => {
|
||||
cy.get('#page-query-report input[data-fieldname="user"]').as('input');
|
||||
cy.get('@input').focus().type('test@erpnext.com', { delay: 100 });
|
||||
|
||||
cy.get('@input').focus().type('test@erpnext.com', { delay: 100 }).blur();
|
||||
cy.wait(300);
|
||||
cy.get('#page-query-report input[data-fieldname="doctype"]').as('input-test');
|
||||
cy.get('@input-test').focus().type('Role', { delay: 100 }).blur();
|
||||
|
||||
cy.get('.datatable').should('exist');
|
||||
cy.get('button').contains('Menu').click({force: true});
|
||||
cy.get('.dropdown-menu li').contains('Add Column').click({force: true});
|
||||
cy.get('.menu-btn-group button').click({ force: true });
|
||||
cy.get('.dropdown-menu li').contains('Add Column').click({ force: true });
|
||||
cy.get('.modal-dialog').should('contain', 'Add Column');
|
||||
cy.get('select[data-fieldname="doctype"]').select("Role", {force: true});
|
||||
cy.get('select[data-fieldname="field"]').select("Role Name", {force: true});
|
||||
cy.get('select[data-fieldname="insert_after"]').select("Name", {force: true});
|
||||
cy.get('button').contains('Submit').click({force: true});
|
||||
cy.get('button').contains('Menu').click({force: true});
|
||||
cy.get('.dropdown-menu li').contains('Save').click({force: true});
|
||||
cy.get('select[data-fieldname="doctype"]').select("Role", { force: true });
|
||||
cy.get('select[data-fieldname="field"]').select("Role Name", { force: true });
|
||||
cy.get('select[data-fieldname="insert_after"]').select("Name", { force: true });
|
||||
cy.get('button').contains('Submit').click({ force: true });
|
||||
cy.get('.menu-btn-group button').click({ force: true });
|
||||
cy.get('.dropdown-menu li').contains('Save').click({ force: true });
|
||||
cy.get('.modal-dialog').should('contain', 'Save Report');
|
||||
|
||||
cy.get('input[data-fieldname="report_name"]').type("Test Report", {delay:100, force: true});
|
||||
cy.get('button').contains('Submit').click({timeout:1000, force: true});
|
||||
cy.get('input[data-fieldname="report_name"]').type("Test Report", { delay: 100, force: true });
|
||||
cy.get('button').contains('Submit').click({ timeout: 1000, force: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -4,17 +4,17 @@ context('Recorder', () => {
|
|||
});
|
||||
|
||||
it('Navigate to Recorder', () => {
|
||||
cy.visit('/desk#workspace/Website');
|
||||
cy.visit('/app');
|
||||
cy.awesomebar('recorder');
|
||||
cy.get('h1').should('contain', 'Recorder');
|
||||
cy.location('hash').should('eq', '#recorder');
|
||||
cy.get('h3').should('contain', 'Recorder');
|
||||
cy.url().should('include', '/recorder/detail');
|
||||
});
|
||||
|
||||
it('Recorder Empty State', () => {
|
||||
cy.visit('/desk#recorder');
|
||||
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');
|
||||
|
|
@ -24,53 +24,49 @@ context('Recorder', () => {
|
|||
});
|
||||
|
||||
it('Recorder Start', () => {
|
||||
cy.visit('/desk#recorder');
|
||||
cy.visit('/app/recorder');
|
||||
cy.get('.primary-action').should('contain', 'Start').click();
|
||||
cy.get('.indicator').should('contain', 'Active').should('have.class', 'green');
|
||||
cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green');
|
||||
|
||||
cy.get('.msg-box').should('contain', 'No Requests');
|
||||
|
||||
cy.server();
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('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.visit('/desk#recorder');
|
||||
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.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('/desk#recorder');
|
||||
cy.visit('/app/recorder');
|
||||
cy.get('.primary-action').should('contain', 'Start').click();
|
||||
|
||||
cy.server();
|
||||
cy.visit('/desk#List/DocType/List');
|
||||
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('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 ');
|
||||
|
||||
// temporarily commenting out theses tests as they seem to be
|
||||
// randomly failing maybe due a backround event
|
||||
cy.visit('/app/recorder');
|
||||
|
||||
// cy.visit('/desk#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,46 +4,44 @@ context('Relative Timeframe', () => {
|
|||
});
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/desk#workspace/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('/desk#List/ToDo/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').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.get('.remove-filter.btn').click();
|
||||
cy.clear_filters();
|
||||
cy.wait('@save_user_settings');
|
||||
});
|
||||
it('sets relative timespan filter for next week and filters list', () => {
|
||||
cy.visit('/desk#List/ToDo/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.btn').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('/desk#workspace/Website');
|
||||
cy.visit('/app/website');
|
||||
cy.insert_doc('DocType', custom_submittable_doctype, true);
|
||||
cy.clear_cache();
|
||||
cy.insert_doc(doctype_name, {
|
||||
|
|
@ -16,15 +16,14 @@ 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.visit(`/desk#List/${doctype_name}/Report`);
|
||||
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');
|
||||
let cell = cy.get('.dt-row-0 > .dt-cell--col-4');
|
||||
// select the cell
|
||||
cell.dblclick();
|
||||
cell.find('input[data-fieldname="enabled"]').check({force: true});
|
||||
cell.find('input[data-fieldname="enabled"]').check({ force: true });
|
||||
cy.get('.dt-row-0 > .dt-cell--col-5').click();
|
||||
cy.wait('@value-update');
|
||||
cy.get('@doc').then(doc => {
|
||||
|
|
|
|||
|
|
@ -13,15 +13,14 @@ context('Table MultiSelect', () => {
|
|||
cy.get('input[data-fieldname="users"]').focus().as('input');
|
||||
cy.get('input[data-fieldname="users"] + ul').should('be.visible');
|
||||
cy.get('@input').type('test{enter}', { delay: 100 });
|
||||
cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value')
|
||||
.first().as('selected-value');
|
||||
cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value .btn-link-to-form')
|
||||
.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');
|
||||
});
|
||||
|
||||
|
|
@ -46,6 +45,6 @@ context('Table MultiSelect', () => {
|
|||
cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
|
||||
cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').as('existing_value');
|
||||
cy.get('@existing_value').find('.btn-link-to-form').click();
|
||||
cy.location('hash').should('contain', 'Form/User/test@erpnext.com');
|
||||
cy.location('pathname').should('contain', '/user/test@erpnext.com');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -244,14 +244,14 @@ Cypress.Commands.add('awesomebar', text => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('new_form', doctype => {
|
||||
let route = `Form/${doctype}/New ${doctype} 1`;
|
||||
cy.visit(`/desk#${route}`);
|
||||
cy.get('body').should('have.attr', 'data-route', route);
|
||||
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');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('go_to_list', doctype => {
|
||||
cy.visit(`/desk#List/${doctype}/List`);
|
||||
cy.visit(`/app/list/${doctype}/list`);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clear_cache', () => {
|
||||
|
|
@ -275,9 +275,8 @@ Cypress.Commands.add('get_open_dialog', () => {
|
|||
});
|
||||
|
||||
Cypress.Commands.add('hide_dialog', () => {
|
||||
cy.get_open_dialog()
|
||||
.find('.btn-modal-close')
|
||||
.click();
|
||||
cy.wait(300);
|
||||
cy.get_open_dialog().find('.btn-modal-close').click();
|
||||
cy.get('.modal:visible').should('not.exist');
|
||||
});
|
||||
|
||||
|
|
@ -307,4 +306,21 @@ Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
|
|||
return res.body.data;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('add_filter', () => {
|
||||
cy.get('.filter-section .filter-button').click();
|
||||
cy.wait(300);
|
||||
cy.get('.filter-popover').should('exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clear_filters', () => {
|
||||
cy.get('.filter-section .filter-button').click();
|
||||
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
|
||||
|
||||
|
|
@ -466,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**.
|
||||
|
||||
|
||||
|
|
@ -492,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:
|
||||
|
|
@ -514,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 = []
|
||||
|
|
@ -964,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:
|
||||
|
|
@ -1632,7 +1635,7 @@ def log_error(message=None, title=_("Error")):
|
|||
method=title)).insert(ignore_permissions=True)
|
||||
|
||||
def get_desk_link(doctype, name):
|
||||
html = '<a href="#Form/{doctype}/{name}" style="font-weight: bold;">{doctype_local} {name}</a>'
|
||||
html = '<a href="/app/Form/{doctype}/{name}" style="font-weight: bold;">{doctype_local} {name}</a>'
|
||||
return html.format(
|
||||
doctype=doctype,
|
||||
name=name,
|
||||
|
|
|
|||
|
|
@ -181,6 +181,9 @@ def make_form_dict(request):
|
|||
else:
|
||||
args = request.form or request.args
|
||||
|
||||
if not isinstance(args, dict):
|
||||
frappe.throw("Invalid request arguments")
|
||||
|
||||
try:
|
||||
frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \
|
||||
for k, v in iteritems(args) })
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ class LoginManager:
|
|||
frappe.local.cookie_manager.set_cookie("system_user", "yes")
|
||||
if not resume:
|
||||
frappe.local.response['message'] = 'Logged In'
|
||||
frappe.local.response["home_page"] = "/desk"
|
||||
frappe.local.response["home_page"] = "/app"
|
||||
|
||||
if not resume:
|
||||
frappe.response["full_name"] = self.full_name
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Tools",
|
||||
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Email",
|
||||
"links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Automation",
|
||||
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Event Streaming",
|
||||
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 14:53:24.980279",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Tools",
|
||||
"modified": "2020-07-21 19:32:18.480700",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "ToDo",
|
||||
"link_to": "ToDo",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Note",
|
||||
"link_to": "Note",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "File",
|
||||
"link_to": "File",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Assignment Rule",
|
||||
"link_to": "Assignment Rule",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Auto Repeat",
|
||||
"link_to": "Auto Repeat",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ frappe.ui.form.on('Auto Repeat', {
|
|||
refresh: function(frm) {
|
||||
// auto repeat message
|
||||
if (frm.is_new()) {
|
||||
let customize_form_link = `<a href="#Form/Customize Form">${__('Customize Form')}</a>`;
|
||||
let customize_form_link = `<a href="/app/customize form">${__('Customize Form')}</a>`;
|
||||
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
|
||||
}
|
||||
|
||||
|
|
@ -106,8 +106,9 @@ frappe.auto_repeat.render_schedule = function(frm) {
|
|||
frm.dashboard.wrapper.empty();
|
||||
frm.dashboard.add_section(
|
||||
frappe.render_template("auto_repeat_schedule", {
|
||||
schedule_details : r.message || []
|
||||
})
|
||||
schedule_details: r.message || []
|
||||
}),
|
||||
__('Auto Repeat Schedule')
|
||||
);
|
||||
frm.dashboard.show();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ 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}
|
||||
|
|
@ -328,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)
|
||||
|
|
|
|||
229
frappe/automation/workspace/tools/tools.json
Normal file
229
frappe/automation/workspace/tools/tools.json
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
{
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 14:53:24.980279",
|
||||
"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": "Tools",
|
||||
"links": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Tools",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "To Do",
|
||||
"link_to": "ToDo",
|
||||
"link_type": "DocType",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Calendar",
|
||||
"link_to": "Event",
|
||||
"link_type": "DocType",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Note",
|
||||
"link_to": "Note",
|
||||
"link_type": "DocType",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Files",
|
||||
"link_to": "File",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Activity",
|
||||
"link_to": "activity",
|
||||
"link_type": "Page",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Email",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Newsletter",
|
||||
"link_to": "Newsletter",
|
||||
"link_type": "DocType",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Email Group",
|
||||
"link_to": "Email Group",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Automation",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Assignment Rule",
|
||||
"link_to": "Assignment Rule",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Milestone",
|
||||
"link_to": "Milestone",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Auto Repeat",
|
||||
"link_to": "Auto Repeat",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Event Streaming",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Event Producer",
|
||||
"link_to": "Event Producer",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Event Consumer",
|
||||
"link_to": "Event Consumer",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Event Update Log",
|
||||
"link_to": "Event Update Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Event Sync Log",
|
||||
"link_to": "Event Sync Log",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Document Type Mapping",
|
||||
"link_to": "Document Type Mapping",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2020-12-01 13:38:39.950350",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "ToDo",
|
||||
"link_to": "ToDo",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Note",
|
||||
"link_to": "Note",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "File",
|
||||
"link_to": "File",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Assignment Rule",
|
||||
"link_to": "Assignment Rule",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Auto Repeat",
|
||||
"link_to": "Auto Repeat",
|
||||
"type": "DocType"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabl
|
|||
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
|
||||
from frappe.model.base_document import get_controller
|
||||
from frappe.social.doctype.post.post import frequently_visited_links
|
||||
from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings
|
||||
from frappe.core.doctype.navbar_settings.navbar_settings import get_navbar_settings, get_app_logo
|
||||
|
||||
def get_bootinfo():
|
||||
"""build and return boot info"""
|
||||
|
|
@ -39,7 +39,7 @@ def get_bootinfo():
|
|||
bootinfo.server_date = frappe.utils.nowdate()
|
||||
|
||||
if frappe.session['user'] != 'Guest':
|
||||
bootinfo.user_info = get_fullnames()
|
||||
bootinfo.user_info = get_user_info()
|
||||
bootinfo.sid = frappe.session['sid']
|
||||
|
||||
bootinfo.modules = {}
|
||||
|
|
@ -48,6 +48,7 @@ def get_bootinfo():
|
|||
bootinfo.letter_heads = get_letter_heads()
|
||||
bootinfo.active_domains = frappe.get_active_domains()
|
||||
bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")]
|
||||
add_layouts(bootinfo)
|
||||
|
||||
bootinfo.module_app = frappe.local.module_app
|
||||
bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})]
|
||||
|
|
@ -61,6 +62,7 @@ def get_bootinfo():
|
|||
doclist.extend(get_meta_bundle("Page"))
|
||||
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1})
|
||||
bootinfo.navbar_settings = get_navbar_settings()
|
||||
bootinfo.notification_settings = get_notification_settings()
|
||||
|
||||
# ipinfo
|
||||
if frappe.session.data.get('ipinfo'):
|
||||
|
|
@ -88,6 +90,8 @@ def get_bootinfo():
|
|||
bootinfo.frequently_visited_links = frequently_visited_links()
|
||||
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
|
||||
bootinfo.additional_filters_config = get_additional_filters_from_hooks()
|
||||
bootinfo.desk_settings = get_desk_settings()
|
||||
bootinfo.app_logo_url = get_app_logo()
|
||||
|
||||
return bootinfo
|
||||
|
||||
|
|
@ -106,11 +110,9 @@ def load_conf_settings(bootinfo):
|
|||
if key in conf: bootinfo[key] = conf.get(key)
|
||||
|
||||
def load_desktop_data(bootinfo):
|
||||
from frappe.config import get_modules_from_all_apps_for_user
|
||||
from frappe.desk.desktop import get_desk_sidebar_items
|
||||
bootinfo.allowed_modules = get_modules_from_all_apps_for_user()
|
||||
bootinfo.allowed_workspaces = get_desk_sidebar_items(flatten=True, cache=False)
|
||||
bootinfo.module_page_map = get_controller("Desk Page").get_module_page_map()
|
||||
bootinfo.allowed_workspaces = get_desk_sidebar_items()
|
||||
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map()
|
||||
bootinfo.dashboards = frappe.get_all("Dashboard")
|
||||
|
||||
def get_allowed_pages(cache=False):
|
||||
|
|
@ -222,19 +224,18 @@ def load_translations(bootinfo):
|
|||
|
||||
bootinfo["__messages"] = messages
|
||||
|
||||
def get_fullnames():
|
||||
"""map of user fullnames"""
|
||||
ret = frappe.db.sql("""select `name`, full_name as fullname,
|
||||
user_image as image, gender, email, username, bio, location, interest, banner_image, allowed_in_mentions
|
||||
from tabUser where enabled=1 and user_type!='Website User'""", as_dict=1)
|
||||
def get_user_info():
|
||||
user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image',
|
||||
'gender', 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type'],
|
||||
filters=dict(enabled=1))
|
||||
|
||||
d = {}
|
||||
for r in ret:
|
||||
# if not r.image:
|
||||
# r.image = get_gravatar(r.name)
|
||||
d[r.name] = r
|
||||
user_info_map = {d.name: d for d in user_info}
|
||||
|
||||
return d
|
||||
admin_data = user_info_map.get('Administrator')
|
||||
if admin_data:
|
||||
user_info_map[admin_data.email] = admin_data
|
||||
|
||||
return user_info_map
|
||||
|
||||
def get_user(bootinfo):
|
||||
"""get user info"""
|
||||
|
|
@ -251,13 +252,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('workspace')
|
||||
|
||||
bootinfo['home_page'] = page.name
|
||||
docs.append(page)
|
||||
bootinfo['home_page'] = 'Workspaces'
|
||||
|
||||
def add_timezone_info(bootinfo):
|
||||
system = bootinfo.sysdefaults.get("time_zone")
|
||||
|
|
@ -273,7 +273,7 @@ def load_print(bootinfo, doclist):
|
|||
|
||||
def load_print_css(bootinfo, print_settings):
|
||||
import frappe.www.printview
|
||||
bootinfo.print_css = frappe.www.printview.get_print_style(print_settings.print_style or "Modern", for_legacy=True)
|
||||
bootinfo.print_css = frappe.www.printview.get_print_style(print_settings.print_style or "Redesign", for_legacy=True)
|
||||
|
||||
def get_unseen_notes():
|
||||
return frappe.db.sql('''select `name`, title, content, notify_on_every_login from `tabNote` where notify_on_login=1
|
||||
|
|
@ -308,3 +308,24 @@ def get_additional_filters_from_hooks():
|
|||
filter_config.update(frappe.get_attr(hook)())
|
||||
|
||||
return filter_config
|
||||
|
||||
def add_layouts(bootinfo):
|
||||
# add routes for readable doctypes
|
||||
bootinfo.doctype_layouts = frappe.get_all('DocType Layout', ['name', 'route', 'document_type'])
|
||||
|
||||
def get_desk_settings():
|
||||
role_list = frappe.get_all('Role', fields=['*'], filters=dict(
|
||||
name=['in', frappe.get_roles()]
|
||||
))
|
||||
desk_settings = {}
|
||||
|
||||
from frappe.core.doctype.role.role import desk_properties
|
||||
|
||||
for role in role_list:
|
||||
for key in desk_properties:
|
||||
desk_settings[key] = desk_settings.get(key) or role.get(key)
|
||||
|
||||
return desk_settings
|
||||
|
||||
def get_notification_settings():
|
||||
return frappe.get_cached_doc('Notification Settings', frappe.session.user)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -67,10 +67,6 @@ def clear_defaults_cache(user=None):
|
|||
elif frappe.flags.in_install!="frappe":
|
||||
frappe.cache().delete_key("defaults")
|
||||
|
||||
def clear_document_cache():
|
||||
frappe.local.document_cache = {}
|
||||
frappe.cache().delete_key("document_cache")
|
||||
|
||||
def clear_doctype_cache(doctype=None):
|
||||
clear_controller_cache(doctype)
|
||||
cache = frappe.cache()
|
||||
|
|
@ -78,9 +74,11 @@ def clear_doctype_cache(doctype=None):
|
|||
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
|
||||
del frappe.local.meta_cache[doctype]
|
||||
|
||||
for key in ('is_table', 'doctype_modules'):
|
||||
for key in ('is_table', 'doctype_modules', 'document_cache'):
|
||||
cache.delete_value(key)
|
||||
|
||||
frappe.local.document_cache = {}
|
||||
|
||||
def clear_single(dt):
|
||||
for name in doctype_cache_keys:
|
||||
cache.hdel(name, dt)
|
||||
|
|
@ -102,15 +100,12 @@ def clear_doctype_cache(doctype=None):
|
|||
for name in doctype_cache_keys:
|
||||
cache.delete_value(name)
|
||||
|
||||
# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
|
||||
clear_document_cache()
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -108,4 +108,4 @@ def is_domain(module):
|
|||
return module.get("category") == "Domains"
|
||||
|
||||
def is_module(module):
|
||||
return module.get("type") == "module"
|
||||
return module.get("type") == "module"
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
data = [
|
||||
{
|
||||
"label": _("Automation"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Assignment Rule",
|
||||
"description": _("Set up rules for user assignments.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Milestone",
|
||||
"description": _("Tracks milestones on the lifecycle of a document if it undergoes multiple stages.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Auto Repeat",
|
||||
"description": _("Automatically generates recurring documents.")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Event Streaming"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Event Producer",
|
||||
"description": _("The site you want to subscribe to for consuming events.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Event Consumer",
|
||||
"description": _("The site which is consuming your events.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Event Update Log",
|
||||
"description": _("Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Event Sync Log",
|
||||
"description": _("Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Document Type Mapping",
|
||||
"description": _("The mapping configuration between two doctypes.")
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
return data
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Documents"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "DocType",
|
||||
"description": _("Models (building blocks) of the Application"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Module Def",
|
||||
"description": _("Groups of DocTypes"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Page",
|
||||
"description": _("Pages in Desk (place holders)"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Report",
|
||||
"description": _("Script or Query reports"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Format",
|
||||
"description": _("Customized Formats for Printing, Email"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Script",
|
||||
"description": _("Client side script extensions in Javascript"),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Logs"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Log",
|
||||
"description": _("Errors in Background Events"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Queue",
|
||||
"description": _("Background Email Queue"),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Background Jobs"),
|
||||
"name": "background_jobs",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Snapshot",
|
||||
"description": _("A log of request errors"),
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Form Customization"),
|
||||
"icon": "fa fa-glass",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Customize Form",
|
||||
"description": _("Change field properties (hide, readonly, permission etc.)")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Field",
|
||||
"description": _("Add fields to forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Custom Script",
|
||||
"description": _("Add custom javascript to forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "DocType",
|
||||
"description": _("Add custom forms.")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Dashboards"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Dashboard",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Dashboard Chart",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Dashboard Chart Source",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Other"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"label": _("Custom Translations"),
|
||||
"name": "Translation",
|
||||
"description": _("Add your own translations")
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Tools"),
|
||||
"icon": "octicon octicon-briefcase",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "ToDo",
|
||||
"label": _("To Do"),
|
||||
"description": _("Documents assigned to you and by you."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Event",
|
||||
"label": _("Calendar"),
|
||||
"link": "List/Event/Calendar",
|
||||
"description": _("Event and other calendars."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Note",
|
||||
"description": _("Private and public Notes."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "File",
|
||||
"label": _("Files"),
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Chat"),
|
||||
"name": "chat",
|
||||
"description": _("Chat messages and other notifications."),
|
||||
"data_doctype": "Communication"
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Activity"),
|
||||
"name": "activity",
|
||||
"description": _("Activity log of all users."),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': _('Email'),
|
||||
'items': [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"description": _("Newsletters to contacts, leads."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Group",
|
||||
"description": _("Email Group List"),
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
# Administration
|
||||
{
|
||||
"module_name": "Desk",
|
||||
"category": "Administration",
|
||||
"label": _("Tools"),
|
||||
"color": "#FFF5A7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-calendar",
|
||||
"type": "module",
|
||||
"description": "Todos, notes, calendar and newsletter."
|
||||
},
|
||||
{
|
||||
"module_name": "Settings",
|
||||
"category": "Administration",
|
||||
"label": _("Settings"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module",
|
||||
"description": "Data import, printing, email and workflows."
|
||||
},
|
||||
{
|
||||
"module_name": "Automation",
|
||||
"category": "Administration",
|
||||
"label": _("Automation"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-gist",
|
||||
"type": "module",
|
||||
"description": "Auto Repeat, Assignment Rule, Milestone Tracking and Event Streaming."
|
||||
},
|
||||
{
|
||||
"module_name": "Users and Permissions",
|
||||
"category": "Administration",
|
||||
"label": _("Users and Permissions"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module",
|
||||
"description": "Setup roles and permissions for users on documents."
|
||||
},
|
||||
{
|
||||
"module_name": "Customization",
|
||||
"category": "Administration",
|
||||
"label": _("Customization"),
|
||||
"color": "#bdc3c7",
|
||||
"reverse": 1,
|
||||
"icon": "octicon octicon-settings",
|
||||
"type": "module",
|
||||
"description": "Customize forms, custom fields, scripts and translations."
|
||||
},
|
||||
{
|
||||
"module_name": "Integrations",
|
||||
"category": "Administration",
|
||||
"label": _("Integrations"),
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"description": "DropBox, Woocomerce, AWS, Shopify and GoCardless."
|
||||
},
|
||||
{
|
||||
"module_name": 'Contacts',
|
||||
"category": "Administration",
|
||||
"label": _("Contacts"),
|
||||
"type": 'module',
|
||||
"icon": "octicon octicon-book",
|
||||
"color": '#ffaedb',
|
||||
"description": "People Contacts and Address Book."
|
||||
},
|
||||
{
|
||||
"module_name": "Core",
|
||||
"category": "Administration",
|
||||
"_label": _("Developer"),
|
||||
"label": "Developer",
|
||||
"color": "#589494",
|
||||
"icon": "octicon octicon-circuit-board",
|
||||
"type": "module",
|
||||
"system_manager": 1,
|
||||
"condition": getattr(frappe.local.conf, 'developer_mode', 0),
|
||||
"description": "Doctypes, dev tools and logs."
|
||||
},
|
||||
|
||||
# Places
|
||||
{
|
||||
"module_name": "Website",
|
||||
"category": "Places",
|
||||
"label": _("Website"),
|
||||
"_label": _("Website"),
|
||||
"color": "#16a085",
|
||||
"icon": "octicon octicon-globe",
|
||||
"type": "module",
|
||||
"description": "Webpages, webforms, blogs and website theme."
|
||||
},
|
||||
{
|
||||
"module_name": 'Social',
|
||||
"category": "Places",
|
||||
"label": _('Social'),
|
||||
"icon": "octicon octicon-heart",
|
||||
"type": 'link',
|
||||
"link": '#social/home',
|
||||
"color": '#FF4136',
|
||||
'standard': 1,
|
||||
'idx': 15,
|
||||
"description": "Build your profile and share posts with other users."
|
||||
},
|
||||
{
|
||||
"module_name": 'Leaderboard',
|
||||
"category": "Places",
|
||||
"label": _('Leaderboard'),
|
||||
"icon": "fa fa-trophy",
|
||||
"type": 'link',
|
||||
"link": '#leaderboard/User',
|
||||
"color": '#FF4136',
|
||||
'standard': 1,
|
||||
},
|
||||
{
|
||||
"module_name": 'dashboard',
|
||||
"category": "Places",
|
||||
"label": _('Dashboard'),
|
||||
"icon": "octicon octicon-graph",
|
||||
"type": "link",
|
||||
"link": "#dashboard",
|
||||
"color": '#FF4136',
|
||||
'standard': 1,
|
||||
'idx': 10
|
||||
},
|
||||
]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
source_link = "https://github.com/frappe/frappe_io"
|
||||
docs_base_url = "/docs"
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Payments"),
|
||||
"icon": "fa fa-star",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Braintree Settings",
|
||||
"description": _("Braintree payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "PayPal Settings",
|
||||
"description": _("PayPal payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Razorpay Settings",
|
||||
"description": _("Razorpay Payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Stripe Settings",
|
||||
"description": _("Stripe payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Paytm Settings",
|
||||
"description": _("Paytm payment gateway settings"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Backup"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Dropbox Settings",
|
||||
"description": _("Dropbox backup settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "S3 Backup Settings",
|
||||
"description": _("S3 Backup Settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Drive",
|
||||
"description": _("Google Drive Backup."),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Authentication"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Social Login Key",
|
||||
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "LDAP Settings",
|
||||
"description": _("Ldap settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "OAuth Client",
|
||||
"description": _("Register OAuth Client App"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "OAuth Provider Settings",
|
||||
"description": _("Settings for OAuth Provider"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Connected App",
|
||||
"description": _("Connect to any OAuth Provider"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Webhook"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Webhook",
|
||||
"description": _("Webhooks calling API requests into web apps"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Slack Webhook URL",
|
||||
"description": _("Slack Webhooks for internal integration"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Google Services"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Settings",
|
||||
"description": _("Google API Settings."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Contacts",
|
||||
"description": _("Google Contacts Integration."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Calendar",
|
||||
"description": _("Google Calendar Integration."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Google Drive",
|
||||
"description": _("Google Drive Integration."),
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.moduleview import add_setup_section
|
||||
|
||||
def get_data():
|
||||
data = [
|
||||
{
|
||||
"label": _("Core"),
|
||||
"icon": "fa fa-wrench",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "System Settings",
|
||||
"label": _("System Settings"),
|
||||
"description": _("Language, Date and Time settings"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Global Defaults",
|
||||
"label": _("Global Defaults"),
|
||||
"description": _("Company, Fiscal Year and Currency defaults"),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Log Settings",
|
||||
"description": _("Log cleanup and notification configuration")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Log",
|
||||
"description": _("Log of error on automated events (scheduler).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Error Snapshot",
|
||||
"description": _("Log of error during requests.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Domain Settings",
|
||||
"label": _("Domain Settings"),
|
||||
"description": _("Enable / Disable Domains"),
|
||||
"hide_count": True
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Data"),
|
||||
"icon": "fa fa-th",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Data Import",
|
||||
"label": _("Import Data"),
|
||||
"icon": "octicon octicon-cloud-upload",
|
||||
"description": _("Import Data from CSV / Excel files.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Data Export",
|
||||
"label": _("Export Data"),
|
||||
"icon": "octicon octicon-cloud-upload",
|
||||
"description": _("Export Data in CSV / Excel format.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Naming Series",
|
||||
"description": _("Set numbering series for transactions."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Rename Tool",
|
||||
"label": _("Bulk Rename"),
|
||||
"description": _("Rename many items by uploading a .csv file."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Bulk Update",
|
||||
"label": _("Bulk Update"),
|
||||
"description": _("Update many values at one time."),
|
||||
"hide_count": True
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"name": "backups",
|
||||
"label": _("Download Backups"),
|
||||
"description": _("List of backups available for download"),
|
||||
"icon": "fa fa-download"
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Deleted Document",
|
||||
"label": _("Deleted Documents"),
|
||||
"description": _("Restore or permanently delete a document.")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Email / Notifications"),
|
||||
"icon": "fa fa-envelope",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Account",
|
||||
"description": _("Add / Manage Email Accounts.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Domain",
|
||||
"description": _("Add / Manage Email Domains.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Notification",
|
||||
"description": _("Setup Notifications based on various criteria.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Template",
|
||||
"description": _("Email Templates for common queries.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Auto Email Report",
|
||||
"description": _("Setup Reports to be emailed at regular intervals"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
"description": _("Create and manage newsletter")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"route": "Form/Notification Settings/{}".format(frappe.session.user),
|
||||
"name": "Notification Settings",
|
||||
"description": _("Configure notifications for mentions, assignments, energy points and more.")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Printing"),
|
||||
"icon": "fa fa-print",
|
||||
"items": [
|
||||
{
|
||||
"type": "page",
|
||||
"label": _("Print Format Builder"),
|
||||
"name": "print-format-builder",
|
||||
"description": _("Drag and Drop tool to build and customize Print Formats.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Settings",
|
||||
"description": _("Set default format, page size, print style etc.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Format",
|
||||
"description": _("Customized HTML Templates for printing transactions.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Print Style",
|
||||
"description": _("Stylesheets for Print Formats")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Workflow"),
|
||||
"icon": "fa fa-random",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow",
|
||||
"description": _("Define workflows for forms.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow State",
|
||||
"description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Workflow Action",
|
||||
"description": _("Actions for workflow (e.g. Approve, Cancel).")
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
add_setup_section(data, "frappe", "website", _("Website"), "fa fa-globe")
|
||||
return data
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Users"),
|
||||
"icon": "fa fa-group",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "User",
|
||||
"description": _("System and Website Users")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role",
|
||||
"description": _("User Roles")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role Profile",
|
||||
"description": _("Role Profile")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Permissions"),
|
||||
"icon": "fa fa-lock",
|
||||
"items": [
|
||||
{
|
||||
"type": "page",
|
||||
"name": "permission-manager",
|
||||
"label": _("Role Permissions Manager"),
|
||||
"icon": "fa fa-lock",
|
||||
"description": _("Set Permissions on Document Types and Roles")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "User Permission",
|
||||
"label": _("User Permissions"),
|
||||
"icon": "fa fa-lock",
|
||||
"description": _("Restrict user for specific document")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Role Permission for Page and Report",
|
||||
"description": _("Set custom roles for page and report")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"doctype": "User",
|
||||
"icon": "fa fa-eye-open",
|
||||
"name": "Permitted Documents For User",
|
||||
"description": _("Check which Documents are readable by a User")
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"doctype": "DocShare",
|
||||
"icon": "fa fa-share",
|
||||
"name": "Document Share Report",
|
||||
"description": _("Report of all document shares")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Logs"),
|
||||
"icon": "fa fa-group",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Activity Log",
|
||||
"label": _("Activity Log"),
|
||||
"description": _("Activity Log by ")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Access Log",
|
||||
"label": _("Access Log"),
|
||||
"description": _("View Log of all print, download and export events")
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return [
|
||||
{
|
||||
"label": _("Web Site"),
|
||||
"icon": "fa fa-star",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Web Page",
|
||||
"description": _("Content web page."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Web Form",
|
||||
"description": _("User editable form on Website."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Sidebar",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Slideshow",
|
||||
"description": _("Embed image slideshows in website pages."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Route Meta",
|
||||
"description": _("Add meta tags to your web pages"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Blog"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Blog Post",
|
||||
"description": _("Single Post (article)."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Blogger",
|
||||
"description": _("A user who posts blogs."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Blog Category",
|
||||
"description": _("Categorize blog posts."),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Setup"),
|
||||
"icon": "fa fa-cog",
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Settings",
|
||||
"description": _("Setup of top navigation bar, footer and logo."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Theme",
|
||||
"description": _("List of themes for Website."),
|
||||
"onboard": 1,
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Website Script",
|
||||
"description": _("Javascript to append to the head section of the page."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "About Us Settings",
|
||||
"description": _("Settings for About Us Page."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Contact Us Settings",
|
||||
"description": _("Settings for Contact Us Page."),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Portal"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Portal Settings",
|
||||
"label": _("Portal Settings"),
|
||||
"onboard": 1,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Knowledge Base"),
|
||||
"items": [
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Help Category",
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Help Article",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
|
|
@ -34,8 +34,8 @@
|
|||
"email_ids",
|
||||
"phone_nos",
|
||||
"contact_details",
|
||||
"is_primary_contact",
|
||||
"links",
|
||||
"is_primary_contact",
|
||||
"more_info",
|
||||
"department",
|
||||
"unsubscribed"
|
||||
|
|
@ -248,8 +248,9 @@
|
|||
"icon": "fa fa-user",
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-06 18:25:28.223693",
|
||||
"modified": "2020-08-27 14:12:09.906719",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Contacts",
|
||||
"name": "Contact",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Data",
|
||||
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Email / Notifications",
|
||||
"links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Website",
|
||||
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Core",
|
||||
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Company, Fiscal Year and Currency defaults\",\n \"hide_count\": true,\n \"label\": \"Global Defaults\",\n \"name\": \"Global Defaults\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Printing",
|
||||
"links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Workflow",
|
||||
"links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]"
|
||||
}
|
||||
],
|
||||
"category": "Modules",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 15:09:40.527211",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 1,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Settings",
|
||||
"modified": "2020-07-14 10:09:09.520557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Settings",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 1,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"icon": "octicon octicon-settings",
|
||||
"label": "System Settings",
|
||||
"link_to": "System Settings",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-print",
|
||||
"label": "Print Settings",
|
||||
"link_to": "Print Settings",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"icon": "fa fa-globe",
|
||||
"label": "Website Settings",
|
||||
"link_to": "Website Settings",
|
||||
"type": "DocType"
|
||||
}
|
||||
],
|
||||
"shortcuts_label": "Settings"
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"cards": [
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Users",
|
||||
"links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Logs",
|
||||
"links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Permissions",
|
||||
"links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]"
|
||||
}
|
||||
],
|
||||
"category": "Administration",
|
||||
"charts": [],
|
||||
"creation": "2020-03-02 15:12:16.754449",
|
||||
"developer_mode_only": 0,
|
||||
"disable_user_customization": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Users",
|
||||
"modified": "2020-04-26 22:36:14.311554",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Users",
|
||||
"owner": "Administrator",
|
||||
"pin_to_bottom": 0,
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "User",
|
||||
"link_to": "User",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Role",
|
||||
"link_to": "Role",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Permission Manager",
|
||||
"link_to": "permission-manager",
|
||||
"type": "Page"
|
||||
},
|
||||
{
|
||||
"label": "User Profile",
|
||||
"link_to": "user-profile",
|
||||
"type": "Page"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -18,10 +18,7 @@ from frappe.exceptions import ImplicitCommitError
|
|||
class Comment(Document):
|
||||
def after_insert(self):
|
||||
self.notify_mentions()
|
||||
|
||||
frappe.publish_realtime('new_communication', self.as_dict(),
|
||||
doctype=self.reference_doctype, docname=self.reference_name,
|
||||
after_commit=True)
|
||||
self.notify_change('add')
|
||||
|
||||
def validate(self):
|
||||
if not self.comment_email:
|
||||
|
|
@ -30,12 +27,30 @@ 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()
|
||||
frappe.publish_realtime('delete_communication', self.as_dict(),
|
||||
doctype= self.reference_doctype, docname = self.reference_name,
|
||||
after_commit=True)
|
||||
self.notify_change('delete')
|
||||
|
||||
def notify_change(self, action):
|
||||
key_map = {
|
||||
'Like': 'like_logs',
|
||||
'Assigned': 'assignment_logs',
|
||||
'Assignment Completed': 'assignment_logs',
|
||||
'Comment': 'comments',
|
||||
'Attachment': 'attachment_logs',
|
||||
'Attachment Removed': 'attachment_logs',
|
||||
}
|
||||
key = key_map.get(self.comment_type)
|
||||
if not key: return
|
||||
|
||||
frappe.publish_realtime('update_docinfo_for_{}_{}'.format(self.reference_doctype, self.reference_name), {
|
||||
'doc': self.as_dict(),
|
||||
'key': key,
|
||||
'action': action
|
||||
}, after_commit=True)
|
||||
|
||||
def remove_comment_from_cache(self):
|
||||
_comments = get_comments_from_parent(self)
|
||||
|
|
|
|||
|
|
@ -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('Document not Relinked')
|
||||
function() {
|
||||
frappe.show_alert({
|
||||
message: __('Document not Relinked'), 'indicator': 'info'
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,10 +99,7 @@ class Communication(Document):
|
|||
frappe.db.set_value("Communication", self.reference_name, "status", "Replied")
|
||||
|
||||
if self.communication_type == "Communication":
|
||||
# send new comment to listening clients
|
||||
frappe.publish_realtime('new_communication', self.as_dict(),
|
||||
doctype=self.reference_doctype, docname=self.reference_name,
|
||||
after_commit=True)
|
||||
self.notify_change('add')
|
||||
|
||||
elif self.communication_type in ("Chat", "Notification", "Bot"):
|
||||
if self.reference_name == frappe.session.user:
|
||||
|
|
@ -125,10 +122,14 @@ class Communication(Document):
|
|||
|
||||
def on_trash(self):
|
||||
if self.communication_type == "Communication":
|
||||
# send delete comment to listening clients
|
||||
frappe.publish_realtime('delete_communication', self.as_dict(),
|
||||
doctype= self.reference_doctype, docname = self.reference_name,
|
||||
after_commit=True)
|
||||
self.notify_change('delete')
|
||||
|
||||
def notify_change(self, action):
|
||||
frappe.publish_realtime('update_docinfo_for_{}_{}'.format(self.reference_doctype, self.reference_name), {
|
||||
'doc': self.as_dict(),
|
||||
'key': 'communications',
|
||||
'action': action
|
||||
}, after_commit=True)
|
||||
|
||||
def set_status(self):
|
||||
if not self.is_new():
|
||||
|
|
@ -244,9 +245,7 @@ class Communication(Document):
|
|||
|
||||
if delivery_status:
|
||||
self.db_set('delivery_status', delivery_status)
|
||||
|
||||
frappe.publish_realtime('update_communication', self.as_dict(),
|
||||
doctype=self.reference_doctype, docname=self.reference_name, after_commit=True)
|
||||
self.notify_change('update')
|
||||
|
||||
# for list views and forms
|
||||
self.notify_update()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ frappe.ui.form.on('Data Import Legacy', {
|
|||
frm.reload_doc();
|
||||
}
|
||||
if (data.progress) {
|
||||
let progress_bar = $(frm.dashboard.progress_area).find(".progress-bar");
|
||||
let progress_bar = $(frm.dashboard.progress_area.body).find(".progress-bar");
|
||||
if (progress_bar) {
|
||||
$(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped");
|
||||
$(progress_bar).css("width", data.progress + "%");
|
||||
|
|
|
|||
|
|
@ -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='/desk#Form/Deleted Document/${docname}'>${docname}</a></li>`;
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ frappe.ui.form.on('DocType', {
|
|||
if (!frm.is_new() && !frm.doc.istable) {
|
||||
if (frm.doc.issingle) {
|
||||
frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => {
|
||||
frappe.set_route('Form', frm.doc.name);
|
||||
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
|
||||
});
|
||||
} else {
|
||||
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
|
||||
frappe.set_route('List', frm.doc.name, 'List');
|
||||
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@
|
|||
"label": "Editable Grid"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.istable && !doc.issingle",
|
||||
"description": "Open a dialog with mandatory fields to create a new record quickly",
|
||||
"fieldname": "quick_entry",
|
||||
|
|
@ -427,7 +427,7 @@
|
|||
"label": "Allow Guest to View"
|
||||
},
|
||||
{
|
||||
"depends_on": "has_web_view",
|
||||
"depends_on": "eval:!doc.istable",
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"label": "Route"
|
||||
|
|
@ -555,7 +555,7 @@
|
|||
},
|
||||
{
|
||||
"group": "Customization",
|
||||
"link_doctype": "Custom Script",
|
||||
"link_doctype": "Client Script",
|
||||
"link_fieldname": "dt"
|
||||
},
|
||||
{
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2020-09-24 13:13:58.227153",
|
||||
"modified": "2021-02-04 15:10:09.227205",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
@ -637,6 +637,7 @@
|
|||
"write": 1
|
||||
}
|
||||
],
|
||||
"route": "doctype",
|
||||
"search_fields": "module",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
|
|
|
|||
|
|
@ -26,7 +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
|
||||
|
|
@ -63,6 +63,37 @@ class DocType(Document):
|
|||
|
||||
self.validate_name()
|
||||
|
||||
self.set_defaults_for_single_and_table()
|
||||
self.scrub_field_names()
|
||||
self.set_default_in_list_view()
|
||||
self.set_default_translatable()
|
||||
self.validate_series()
|
||||
self.validate_document_type()
|
||||
validate_fields(self)
|
||||
|
||||
if not self.istable:
|
||||
validate_permissions(self)
|
||||
|
||||
self.make_amendable()
|
||||
self.make_repeatable()
|
||||
self.validate_nestedset()
|
||||
self.validate_website()
|
||||
validate_links_table_fieldnames(self)
|
||||
|
||||
if not self.is_new():
|
||||
self.before_update = frappe.get_doc('DocType', self.name)
|
||||
self.setup_fields_to_fetch()
|
||||
|
||||
check_email_append_to(self)
|
||||
|
||||
if self.default_print_format and not self.custom:
|
||||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form'))
|
||||
|
||||
def after_insert(self):
|
||||
# clear user cache so that on the next reload this doctype is included in boot
|
||||
clear_user_cache(frappe.session.user)
|
||||
|
||||
def set_defaults_for_single_and_table(self):
|
||||
if self.issingle:
|
||||
self.allow_import = 0
|
||||
self.is_submittable = 0
|
||||
|
|
@ -72,44 +103,6 @@ class DocType(Document):
|
|||
self.allow_import = 0
|
||||
self.permissions = []
|
||||
|
||||
self.scrub_field_names()
|
||||
self.set_default_in_list_view()
|
||||
self.set_default_translatable()
|
||||
self.validate_series()
|
||||
self.validate_document_type()
|
||||
validate_fields(self)
|
||||
|
||||
if self.istable:
|
||||
# no permission records for child table
|
||||
self.permissions = []
|
||||
else:
|
||||
validate_permissions(self)
|
||||
|
||||
self.make_amendable()
|
||||
self.make_repeatable()
|
||||
self.validate_nestedset()
|
||||
self.validate_website()
|
||||
self.validate_links_table_fieldnames()
|
||||
|
||||
if not self.is_new():
|
||||
self.before_update = frappe.get_doc('DocType', self.name)
|
||||
|
||||
if not self.is_new():
|
||||
self.setup_fields_to_fetch()
|
||||
|
||||
check_email_append_to(self)
|
||||
|
||||
if self.default_print_format and not self.custom:
|
||||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form'))
|
||||
|
||||
if frappe.conf.get('developer_mode'):
|
||||
self.owner = 'Administrator'
|
||||
self.modified_by = 'Administrator'
|
||||
|
||||
def after_insert(self):
|
||||
# clear user cache so that on the next reload this doctype is included in boot
|
||||
clear_user_cache(frappe.session.user)
|
||||
|
||||
def set_default_in_list_view(self):
|
||||
'''Set default in-list-view for first 4 mandatory fields'''
|
||||
if not [d.fieldname for d in self.fields if d.in_list_view]:
|
||||
|
|
@ -134,6 +127,10 @@ class DocType(Document):
|
|||
if not frappe.conf.get("developer_mode") and not self.custom:
|
||||
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError)
|
||||
|
||||
if frappe.conf.get('developer_mode'):
|
||||
self.owner = 'Administrator'
|
||||
self.modified_by = 'Administrator'
|
||||
|
||||
def setup_fields_to_fetch(self):
|
||||
'''Setup query to update values for newly set fetch values'''
|
||||
try:
|
||||
|
|
@ -192,6 +189,9 @@ class DocType(Document):
|
|||
|
||||
def validate_website(self):
|
||||
"""Ensure that website generator has field 'route'"""
|
||||
if self.route:
|
||||
self.route = self.route.strip('/')
|
||||
|
||||
if self.has_web_view:
|
||||
# route field must be present
|
||||
if not 'route' in [d.fieldname for d in self.fields]:
|
||||
|
|
@ -278,7 +278,6 @@ class DocType(Document):
|
|||
|
||||
def on_update(self):
|
||||
"""Update database schema, make controller templates if `custom` is not set and clear cache."""
|
||||
self.delete_duplicate_custom_fields()
|
||||
try:
|
||||
frappe.db.updatedb(self.name, Meta(self))
|
||||
except Exception as e:
|
||||
|
|
@ -323,18 +322,6 @@ class DocType(Document):
|
|||
|
||||
clear_linked_doctype_cache()
|
||||
|
||||
def delete_duplicate_custom_fields(self):
|
||||
if not (frappe.db.table_exists(self.name) and frappe.db.table_exists("Custom Field")):
|
||||
return
|
||||
|
||||
fields = [d.fieldname for d in self.fields if d.fieldtype in data_fieldtypes]
|
||||
if fields:
|
||||
frappe.db.sql('''delete from
|
||||
`tabCustom Field`
|
||||
where
|
||||
dt = {0} and fieldname in ({1})
|
||||
'''.format('%s', ', '.join(['%s'] * len(fields))), tuple([self.name] + fields), as_dict=True)
|
||||
|
||||
def sync_global_search(self):
|
||||
'''If global search settings are changed, rebuild search properties for this table'''
|
||||
global_search_fields_before_update = [d.fieldname for d in
|
||||
|
|
@ -669,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
|
||||
|
|
@ -677,24 +664,24 @@ 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)
|
||||
|
||||
def validate_links_table_fieldnames(self):
|
||||
"""Validate fieldnames in Links table"""
|
||||
if frappe.flags.in_patch: return
|
||||
if frappe.flags.in_fixtures: return
|
||||
if not self.links: return
|
||||
|
||||
for index, link in enumerate(self.links):
|
||||
meta = frappe.get_meta(link.link_doctype)
|
||||
if not meta.get_field(link.link_fieldname):
|
||||
message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
|
||||
frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))
|
||||
validate_route_conflict(self.doctype, self.name)
|
||||
|
||||
def validate_links_table_fieldnames(meta):
|
||||
"""Validate fieldnames in Links table"""
|
||||
if frappe.flags.in_patch: return
|
||||
if frappe.flags.in_fixtures: return
|
||||
if not meta.links: return
|
||||
|
||||
for index, link in enumerate(meta.links):
|
||||
link_meta = frappe.get_meta(link.link_doctype)
|
||||
if not link_meta.get_field(link.link_fieldname):
|
||||
message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
|
||||
frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))
|
||||
|
||||
def validate_fields_for_doctype(doctype):
|
||||
doc = frappe.get_doc("DocType", doctype)
|
||||
doc.delete_duplicate_custom_fields()
|
||||
validate_fields(frappe.get_meta(doctype, cached=False))
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
validate_links_table_fieldnames(meta)
|
||||
validate_fields(meta)
|
||||
|
||||
# this is separate because it is also called via custom field
|
||||
def validate_fields(meta):
|
||||
|
|
|
|||
7
frappe/core/doctype/doctype/patches/set_route.py
Normal file
7
frappe/core/doctype/doctype/patches/set_route.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import frappe
|
||||
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', slug(doctype.name), update_modified = False)
|
||||
|
|
@ -5,12 +5,17 @@ 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')
|
||||
|
||||
|
||||
class TestDocType(unittest.TestCase):
|
||||
def test_validate_name(self):
|
||||
self.assertRaises(frappe.NameError, new_doctype("_Some DocType").insert)
|
||||
|
|
@ -459,7 +464,7 @@ class TestDocType(unittest.TestCase):
|
|||
'link_doctype': "User",
|
||||
'link_fieldname': "first_name"
|
||||
})
|
||||
doc.validate_links_table_fieldnames() # no error
|
||||
validate_links_table_fieldnames(doc) # no error
|
||||
doc.links = [] # reset links table
|
||||
|
||||
# check invalid doctype
|
||||
|
|
@ -467,7 +472,7 @@ class TestDocType(unittest.TestCase):
|
|||
'link_doctype': "User2",
|
||||
'link_fieldname': "first_name"
|
||||
})
|
||||
self.assertRaises(frappe.DoesNotExistError, doc.validate_links_table_fieldnames)
|
||||
self.assertRaises(frappe.DoesNotExistError, validate_links_table_fieldnames, doc)
|
||||
doc.links = [] # reset links table
|
||||
|
||||
# check invalid fieldname
|
||||
|
|
@ -475,7 +480,7 @@ class TestDocType(unittest.TestCase):
|
|||
'link_doctype': "User",
|
||||
'link_fieldname': "a_field_that_does_not_exists"
|
||||
})
|
||||
self.assertRaises(InvalidFieldNameError, doc.validate_links_table_fieldnames)
|
||||
self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, doc)
|
||||
|
||||
|
||||
def new_doctype(name, unique=0, depends_on='', fields=None):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def has_unseen_error_log(user):
|
|||
def _get_response(show_alert=True):
|
||||
return {
|
||||
'show_alert': True,
|
||||
'message': _("You have unseen {0}").format('<a href="/desk#List/Error%20Log/List"> Error Logs </a>')
|
||||
'message': _("You have unseen {0}").format('<a href="/app/List/Error%20Log/List"> Error Logs </a>')
|
||||
}
|
||||
|
||||
if frappe.db.sql_list("select name from `tabError Log` where seen = 0 limit 1"):
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
"link_fieldname": "module"
|
||||
},
|
||||
{
|
||||
"link_doctype": "Desk Page",
|
||||
"link_doctype": "Workspace",
|
||||
"link_fieldname": "module"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
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')
|
||||
|
|
@ -23,9 +23,9 @@ class NavbarSettings(Document):
|
|||
if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)):
|
||||
frappe.throw(_("Please hide the standard navbar items instead of deleting them"))
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_app_logo():
|
||||
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo')
|
||||
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo', cache=True)
|
||||
if not app_logo:
|
||||
app_logo = frappe.get_hooks('app_logo_url')[-1]
|
||||
|
||||
|
|
|
|||
|
|
@ -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,6 +34,8 @@ class Page(Document):
|
|||
self.name += '-' + str(cnt)
|
||||
|
||||
def validate(self):
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
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:
|
||||
role_doc.set(key, role_doc.desk_access)
|
||||
role_doc.save()
|
||||
|
|
@ -13,7 +13,19 @@
|
|||
"column_break_4",
|
||||
"disabled",
|
||||
"desk_access",
|
||||
"two_factor_auth"
|
||||
"two_factor_auth",
|
||||
"navigation_settings_section",
|
||||
"search_bar",
|
||||
"notifications",
|
||||
"chat",
|
||||
"list_settings_section",
|
||||
"list_sidebar",
|
||||
"bulk_actions",
|
||||
"view_switcher",
|
||||
"form_settings_section",
|
||||
"form_sidebar",
|
||||
"timeline",
|
||||
"dashboard"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -60,12 +72,82 @@
|
|||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "navigation_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Navigation Settings"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "search_bar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Search Bar"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "chat",
|
||||
"fieldtype": "Check",
|
||||
"label": "Chat"
|
||||
},
|
||||
{
|
||||
"fieldname": "list_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "List Settings"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "list_sidebar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sidebar"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "bulk_actions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bulk Actions"
|
||||
},
|
||||
{
|
||||
"fieldname": "form_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Form Settings"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "form_sidebar",
|
||||
"fieldtype": "Check",
|
||||
"label": "Sidebar"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "timeline",
|
||||
"fieldtype": "Check",
|
||||
"label": "Timeline"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "dashboard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Dashboard"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "view_switcher",
|
||||
"fieldtype": "Check",
|
||||
"label": "View Switcher"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "notifications",
|
||||
"fieldtype": "Check",
|
||||
"label": "Notifications"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bookmark",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-06 15:42:59.036960",
|
||||
"modified": "2020-12-03 14:08:38.181035",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Role",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import frappe
|
|||
|
||||
from frappe.model.document import Document
|
||||
|
||||
desk_properties = ("search_bar", "notifications", "chat", "list_sidebar",
|
||||
"bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard")
|
||||
|
||||
class Role(Document):
|
||||
def before_rename(self, old, new, merge=False):
|
||||
if old in ("Guest", "Administrator", "System Manager", "All"):
|
||||
|
|
@ -16,11 +19,28 @@ class Role(Document):
|
|||
|
||||
def validate(self):
|
||||
if self.disabled:
|
||||
if self.name in ("Guest", "Administrator", "System Manager", "All"):
|
||||
frappe.throw(frappe._("Standard roles cannot be disabled"))
|
||||
else:
|
||||
frappe.db.sql("delete from `tabHas Role` where role = %s", self.name)
|
||||
frappe.clear_cache()
|
||||
self.disable_role()
|
||||
else:
|
||||
self.set_desk_properties()
|
||||
|
||||
def disable_role(self):
|
||||
if self.name in ("Guest", "Administrator", "System Manager", "All"):
|
||||
frappe.throw(frappe._("Standard roles cannot be disabled"))
|
||||
else:
|
||||
self.remove_roles()
|
||||
|
||||
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)
|
||||
|
||||
def remove_roles(self):
|
||||
frappe.db.sql("delete from `tabHas Role` where role = %s", self.name)
|
||||
frappe.clear_cache()
|
||||
|
||||
def on_update(self):
|
||||
'''update system user desk access if this has changed in this update'''
|
||||
|
|
|
|||
|
|
@ -3,32 +3,32 @@
|
|||
|
||||
frappe.ui.form.on('Role Permission for Page and Report', {
|
||||
setup: function(frm) {
|
||||
frm.trigger("set_queries")
|
||||
frm.trigger("set_queries");
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.role_area.hide();
|
||||
frm.events.add_custom_buttons(frm);
|
||||
frm.events.setup_buttons(frm);
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
setup_buttons: function(frm) {
|
||||
frm.clear_custom_buttons();
|
||||
if(frm.doc.set_role_for && frm.doc[frappe.model.scrub(frm.doc.set_role_for)]) {
|
||||
frm.page.clear_actions();
|
||||
if (frm.doc.set_role_for && frm.doc[frappe.model.scrub(frm.doc.set_role_for)]) {
|
||||
frm.add_custom_button(__("Reset to defaults"), function() {
|
||||
frm.trigger("reset_roles");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Update"), function() {
|
||||
frm.page.set_primary_action(__("Update"), () => {
|
||||
frm.trigger("update_report_page_data");
|
||||
}).addClass('btn-primary');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(!frm.roles_editor) {
|
||||
frm.role_area = $('<div style="min-height: 300px">')
|
||||
.appendTo(frm.fields_dict.roles_html.wrapper);
|
||||
if (!frm.roles_editor) {
|
||||
frm.role_area = $(frm.fields_dict.roles_html.wrapper);
|
||||
frm.roles_editor = new frappe.RoleEditor(frm.role_area, frm);
|
||||
}
|
||||
},
|
||||
|
|
@ -54,17 +54,17 @@ frappe.ui.form.on('Role Permission for Page and Report', {
|
|||
},
|
||||
|
||||
page: function(frm) {
|
||||
frm.events.add_custom_buttons(frm);
|
||||
if(frm.doc.page) {
|
||||
frm.events.setup_buttons(frm);
|
||||
if (frm.doc.page) {
|
||||
frm.trigger("set_report_page_data");
|
||||
} else {
|
||||
frm.trigger("set_role_for");
|
||||
}
|
||||
},
|
||||
|
||||
report: function(frm){
|
||||
frm.events.add_custom_buttons(frm);
|
||||
if(frm.doc.report) {
|
||||
report: function(frm) {
|
||||
frm.events.setup_buttons(frm);
|
||||
if (frm.doc.report) {
|
||||
frm.trigger("set_report_page_data");
|
||||
} else {
|
||||
frm.trigger("set_role_for");
|
||||
|
|
|
|||
|
|
@ -3,20 +3,18 @@
|
|||
|
||||
frappe.ui.form.on('Role Profile', {
|
||||
refresh: function(frm) {
|
||||
if(has_common(frappe.user_roles, ["Administrator", "System Manager"])) {
|
||||
if(!frm.roles_editor) {
|
||||
var role_area = $('<div style="min-height: 300px">')
|
||||
.appendTo(frm.fields_dict.roles_html.wrapper);
|
||||
if (has_common(frappe.user_roles, ["Administrator", "System Manager"])) {
|
||||
if (!frm.roles_editor) {
|
||||
const role_area = $(frm.fields_dict.roles_html.wrapper);
|
||||
frm.roles_editor = new frappe.RoleEditor(role_area, frm);
|
||||
frm.roles_editor.show();
|
||||
} else {
|
||||
frm.roles_editor.show();
|
||||
}
|
||||
frm.roles_editor.show();
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
validate: function(frm) {
|
||||
if(frm.roles_editor) {
|
||||
if (frm.roles_editor) {
|
||||
frm.roles_editor.set_roles_in_table();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from typing import Dict, List
|
||||
|
||||
import frappe, json
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -11,12 +12,13 @@ from datetime import datetime
|
|||
from croniter import croniter
|
||||
from frappe.utils.background_jobs import enqueue, get_jobs
|
||||
|
||||
|
||||
class ScheduledJobType(Document):
|
||||
def autoname(self):
|
||||
self.name = '.'.join(self.method.split('.')[-2:])
|
||||
self.name = ".".join(self.method.split(".")[-2:])
|
||||
|
||||
def validate(self):
|
||||
if self.frequency != 'All':
|
||||
if self.frequency != "All":
|
||||
# force logging for all events other than continuous ones (ALL)
|
||||
self.create_log = 1
|
||||
|
||||
|
|
@ -84,7 +86,7 @@ class ScheduledJobType(Document):
|
|||
|
||||
def log_status(self, status):
|
||||
# log file
|
||||
frappe.logger("scheduler").info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site))
|
||||
frappe.logger("scheduler").info(f"Scheduled Job {status}: {self.method} for {frappe.local.site}")
|
||||
self.update_scheduler_log(status)
|
||||
|
||||
def update_scheduler_log(self, status):
|
||||
|
|
@ -111,28 +113,28 @@ class ScheduledJobType(Document):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_event(doc):
|
||||
frappe.only_for('System Manager')
|
||||
def execute_event(doc: str):
|
||||
frappe.only_for("System Manager")
|
||||
doc = json.loads(doc)
|
||||
frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue(force=True)
|
||||
frappe.get_doc("Scheduled Job Type", doc.get("name")).enqueue(force=True)
|
||||
|
||||
|
||||
def run_scheduled_job(job_type):
|
||||
'''This is a wrapper function that runs a hooks.scheduler_events method'''
|
||||
def run_scheduled_job(job_type: str):
|
||||
"""This is a wrapper function that runs a hooks.scheduler_events method"""
|
||||
try:
|
||||
frappe.get_doc('Scheduled Job Type', dict(method=job_type)).execute()
|
||||
frappe.get_doc("Scheduled Job Type", dict(method=job_type)).execute()
|
||||
except Exception:
|
||||
print(frappe.get_traceback())
|
||||
|
||||
|
||||
def sync_jobs(hooks=None):
|
||||
def sync_jobs(hooks: Dict = None):
|
||||
frappe.reload_doc("core", "doctype", "scheduled_job_type")
|
||||
scheduler_events = hooks or frappe.get_hooks("scheduler_events")
|
||||
all_events = insert_events(scheduler_events)
|
||||
clear_events(all_events)
|
||||
|
||||
|
||||
def insert_events(scheduler_events):
|
||||
def insert_events(scheduler_events: Dict) -> List:
|
||||
cron_jobs, event_jobs = [], []
|
||||
for event_type in scheduler_events:
|
||||
events = scheduler_events.get(event_type)
|
||||
|
|
@ -144,7 +146,7 @@ def insert_events(scheduler_events):
|
|||
return cron_jobs + event_jobs
|
||||
|
||||
|
||||
def insert_cron_jobs(events):
|
||||
def insert_cron_jobs(events: Dict) -> List:
|
||||
cron_jobs = []
|
||||
for cron_format in events:
|
||||
for event in events.get(cron_format):
|
||||
|
|
@ -153,25 +155,29 @@ def insert_cron_jobs(events):
|
|||
return cron_jobs
|
||||
|
||||
|
||||
def insert_event_jobs(events, event_type):
|
||||
def insert_event_jobs(events: List, event_type: str) -> List:
|
||||
event_jobs = []
|
||||
for event in events:
|
||||
event_jobs.append(event)
|
||||
frequency = event_type.replace('_', ' ').title()
|
||||
frequency = event_type.replace("_", " ").title()
|
||||
insert_single_event(frequency, event)
|
||||
return event_jobs
|
||||
|
||||
|
||||
def insert_single_event(frequency, event, cron_format=None):
|
||||
def insert_single_event(frequency: str, event: str, cron_format: str = None):
|
||||
cron_expr = {"cron_format": cron_format} if cron_format else {}
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Scheduled Job Type",
|
||||
"method": event,
|
||||
"cron_format": cron_format,
|
||||
"frequency": frequency
|
||||
})
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Scheduled Job Type",
|
||||
"method": event,
|
||||
"cron_format": cron_format,
|
||||
"frequency": frequency,
|
||||
}
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }):
|
||||
if not frappe.db.exists(
|
||||
"Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr}
|
||||
):
|
||||
try:
|
||||
doc.insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
|
|
@ -179,7 +185,12 @@ def insert_single_event(frequency, event, cron_format=None):
|
|||
doc.insert()
|
||||
|
||||
|
||||
def clear_events(all_events):
|
||||
for event in frappe.get_all("Scheduled Job Type", ("name", "method")):
|
||||
if event.method not in all_events:
|
||||
def clear_events(all_events: List):
|
||||
for event in frappe.get_all(
|
||||
"Scheduled Job Type", fields=["name", "method", "server_script"]
|
||||
):
|
||||
is_server_script = event.server_script
|
||||
is_defined_in_hooks = event.method in all_events
|
||||
|
||||
if not (is_defined_in_hooks or is_server_script):
|
||||
frappe.delete_doc("Scheduled Job Type", event.name)
|
||||
|
|
|
|||
|
|
@ -6,46 +6,11 @@ frappe.ui.form.on('Server Script', {
|
|||
frm.trigger('setup_help');
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.script_type === 'Scheduler Event' && !frm.doc.disabled) {
|
||||
frm.add_custom_button('Schedule Script', function() {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: "Schedule Script Execution",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "event_type",
|
||||
label: __('Select Event Type'),
|
||||
fieldtype: "Select",
|
||||
options: "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long"
|
||||
},
|
||||
],
|
||||
primary_action_label: __('Schedule Script'),
|
||||
primary_action: () => {
|
||||
d.get_primary_btn().attr('disabled', true);
|
||||
var data = d.get_values();
|
||||
d.hide();
|
||||
if(data) {
|
||||
frm.events.schedule_script(frm, data);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
d.show();
|
||||
|
||||
});
|
||||
if (frm.doc.script_type != 'Scheduler Event') {
|
||||
frm.dashboard.hide();
|
||||
}
|
||||
},
|
||||
|
||||
schedule_script(frm, data) {
|
||||
frm.call({
|
||||
method: "frappe.core.doctype.server_script.server_script.setup_scheduler_events",
|
||||
args: {
|
||||
'script_name': frm.doc.name,
|
||||
'frequency': data.event_type
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setup_help(frm) {
|
||||
frm.get_field('help_html').html(`
|
||||
<h4>DocType Event</h4>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"field_order": [
|
||||
"script_type",
|
||||
"reference_doctype",
|
||||
"event_frequency",
|
||||
"doctype_event",
|
||||
"api_method",
|
||||
"allow_guest",
|
||||
|
|
@ -84,11 +85,24 @@
|
|||
{
|
||||
"fieldname": "help_html",
|
||||
"fieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.script_type == \"Scheduler Event\"",
|
||||
"fieldname": "event_frequency",
|
||||
"fieldtype": "Select",
|
||||
"label": "Event Frequency",
|
||||
"mandatory_depends_on": "eval:doc.script_type == \"Scheduler Event\"",
|
||||
"options": "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-03 18:50:14.767595",
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Scheduled Job Type",
|
||||
"link_fieldname": "server_script"
|
||||
}
|
||||
],
|
||||
"modified": "2021-02-18 12:36:19.803425",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import ast
|
||||
from typing import Dict, List
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -14,67 +15,146 @@ from frappe import _
|
|||
|
||||
class ServerScript(Document):
|
||||
def validate(self):
|
||||
frappe.only_for('Script Manager', True)
|
||||
frappe.only_for("Script Manager", True)
|
||||
self.validate_script()
|
||||
self.sync_scheduled_jobs()
|
||||
self.clear_scheduled_events()
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().delete_value("server_script_map")
|
||||
self.sync_scheduler_events()
|
||||
|
||||
def on_trash(self):
|
||||
if self.script_type == "Scheduler Event":
|
||||
for job in self.scheduled_jobs:
|
||||
frappe.delete_doc("Scheduled Job Type", job.name)
|
||||
|
||||
@property
|
||||
def scheduled_jobs(self) -> List[Dict[str, str]]:
|
||||
return frappe.get_all(
|
||||
"Scheduled Job Type",
|
||||
filters={"server_script": self.name},
|
||||
fields=["name", "stopped"],
|
||||
)
|
||||
|
||||
def validate_script(self):
|
||||
"""Utilizes the ast module to check for syntax errors
|
||||
"""
|
||||
ast.parse(self.script)
|
||||
|
||||
@staticmethod
|
||||
def on_update():
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
def sync_scheduled_jobs(self):
|
||||
"""Sync Scheduled Job Type statuses if Server Script's disabled status is changed
|
||||
"""
|
||||
if self.script_type != "Scheduler Event" or not self.has_value_changed("disabled"):
|
||||
return
|
||||
|
||||
def execute_method(self):
|
||||
if self.script_type == 'API':
|
||||
# validate if guest is allowed
|
||||
if frappe.session.user == 'Guest' and not self.allow_guest:
|
||||
raise frappe.PermissionError
|
||||
_globals, _locals = safe_exec(self.script)
|
||||
return _globals.frappe.flags # output can be stored in flags
|
||||
else:
|
||||
# wrong report type!
|
||||
for scheduled_job in self.scheduled_jobs:
|
||||
if bool(scheduled_job.stopped) != bool(self.disabled):
|
||||
job = frappe.get_doc("Scheduled Job Type", scheduled_job.name)
|
||||
job.stopped = self.disabled
|
||||
job.save()
|
||||
|
||||
def sync_scheduler_events(self):
|
||||
"""Create or update Scheduled Job Type documents for Scheduler Event Server Scripts
|
||||
"""
|
||||
if not self.disabled and self.event_frequency and self.script_type == "Scheduler Event":
|
||||
setup_scheduler_events(script_name=self.name, frequency=self.event_frequency)
|
||||
|
||||
def clear_scheduled_events(self):
|
||||
"""Deletes existing scheduled jobs by Server Script if self.event_frequency has changed
|
||||
"""
|
||||
if self.script_type == "Scheduler Event" and self.has_value_changed("event_frequency"):
|
||||
for scheduled_job in self.scheduled_jobs:
|
||||
frappe.delete_doc("Scheduled Job Type", scheduled_job.name)
|
||||
|
||||
def execute_method(self) -> Dict:
|
||||
"""Specific to API endpoint Server Scripts
|
||||
|
||||
Raises:
|
||||
frappe.DoesNotExistError: If self.script_type is not API
|
||||
frappe.PermissionError: If self.allow_guest is unset for API accessed by Guest user
|
||||
|
||||
Returns:
|
||||
dict: Evaluates self.script with frappe.utils.safe_exec.safe_exec and returns the flags set in it's safe globals
|
||||
"""
|
||||
# wrong report type!
|
||||
if self.script_type != "API":
|
||||
raise frappe.DoesNotExistError
|
||||
|
||||
def execute_doc(self, doc):
|
||||
# execute event
|
||||
safe_exec(self.script, None, dict(doc = doc))
|
||||
# validate if guest is allowed
|
||||
if frappe.session.user == "Guest" and not self.allow_guest:
|
||||
raise frappe.PermissionError
|
||||
|
||||
# output can be stored in flags
|
||||
_globals, _locals = safe_exec(self.script)
|
||||
return _globals.frappe.flags
|
||||
|
||||
def execute_doc(self, doc: Document):
|
||||
"""Specific to Document Event triggered Server Scripts
|
||||
|
||||
Args:
|
||||
doc (Document): Executes script with for a certain document's events
|
||||
"""
|
||||
safe_exec(self.script, _locals={"doc": doc})
|
||||
|
||||
def execute_scheduled_method(self):
|
||||
if self.script_type == 'Scheduler Event':
|
||||
safe_exec(self.script)
|
||||
else:
|
||||
# wrong report type!
|
||||
"""Specific to Scheduled Jobs via Server Scripts
|
||||
|
||||
Raises:
|
||||
frappe.DoesNotExistError: If script type is not a scheduler event
|
||||
"""
|
||||
if self.script_type != "Scheduler Event":
|
||||
raise frappe.DoesNotExistError
|
||||
|
||||
def get_permission_query_conditions(self, user):
|
||||
safe_exec(self.script)
|
||||
|
||||
def get_permission_query_conditions(self, user: str) -> List[str]:
|
||||
"""Specific to Permission Query Server Scripts
|
||||
|
||||
Args:
|
||||
user (str): Takes user email to execute script and return list of conditions
|
||||
|
||||
Returns:
|
||||
list: Returns list of conditions defined by rules in self.script
|
||||
"""
|
||||
locals = {"user": user, "conditions": ""}
|
||||
safe_exec(self.script, None, locals)
|
||||
if locals["conditions"]:
|
||||
return locals["conditions"]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def setup_scheduler_events(script_name, frequency):
|
||||
method = frappe.scrub('{0}-{1}'.format(script_name, frequency))
|
||||
scheduled_script = frappe.db.get_value('Scheduled Job Type',
|
||||
dict(method=method))
|
||||
"""Creates or Updates Scheduled Job Type documents based on the specified script name and frequency
|
||||
|
||||
Args:
|
||||
script_name (str): Name of the Server Script document
|
||||
frequency (str): Event label compatible with the Frappe scheduler
|
||||
"""
|
||||
method = frappe.scrub(f"{script_name}-{frequency}")
|
||||
scheduled_script = frappe.db.get_value("Scheduled Job Type", {"method": method})
|
||||
|
||||
if not scheduled_script:
|
||||
doc = frappe.get_doc(dict(
|
||||
doctype = 'Scheduled Job Type',
|
||||
method = method,
|
||||
frequency = frequency,
|
||||
server_script = script_name
|
||||
))
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Scheduled Job Type",
|
||||
"method": method,
|
||||
"frequency": frequency,
|
||||
"server_script": script_name,
|
||||
}
|
||||
).insert()
|
||||
|
||||
doc.insert()
|
||||
|
||||
frappe.msgprint(_('Enabled scheduled execution for script {0}').format(script_name))
|
||||
frappe.msgprint(_("Enabled scheduled execution for script {0}").format(script_name))
|
||||
|
||||
else:
|
||||
doc = frappe.get_doc('Scheduled Job Type', scheduled_script)
|
||||
doc.update(dict(
|
||||
doctype = 'Scheduled Job Type',
|
||||
method = method,
|
||||
frequency = frequency,
|
||||
server_script = script_name
|
||||
))
|
||||
doc = frappe.get_doc("Scheduled Job Type", scheduled_script)
|
||||
|
||||
if doc.frequency == frequency:
|
||||
return
|
||||
|
||||
doc.frequency = frequency
|
||||
doc.save()
|
||||
|
||||
frappe.msgprint(_('Scheduled execution for script {0} has updated').format(script_name))
|
||||
frappe.msgprint(
|
||||
_("Scheduled execution for script {0} has updated").format(script_name)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class TestServerScript(unittest.TestCase):
|
|||
def tearDownClass(cls):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql('truncate `tabServer Script`')
|
||||
frappe.cache().delete_key('server_script_map')
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
|
||||
def setUp(self):
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"localization",
|
||||
"app_name",
|
||||
"country",
|
||||
"language",
|
||||
"column_break_3",
|
||||
|
|
@ -462,6 +463,19 @@
|
|||
"fieldtype": "Section Break",
|
||||
"label": "Prepared Report"
|
||||
},
|
||||
{
|
||||
"default": "Frappe",
|
||||
"fieldname": "app_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "App Name"
|
||||
},
|
||||
{
|
||||
"default": "3",
|
||||
"description": "Hourly rate limit for generating password reset links",
|
||||
"fieldname": "password_reset_limit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Password Reset Link Generation Limit"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "strip_exif_metadata_from_uploaded_images",
|
||||
|
|
@ -472,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",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, unittest
|
||||
import frappe, unittest, uuid
|
||||
|
||||
from frappe.model.delete_doc import delete_doc
|
||||
from frappe.utils.data import today, add_to_date
|
||||
|
|
@ -20,6 +20,7 @@ class TestUser(unittest.TestCase):
|
|||
frappe.db.set_value("System Settings", "System Settings", "enable_password_policy", 0)
|
||||
frappe.db.set_value("System Settings", "System Settings", "minimum_password_score", "")
|
||||
frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 3)
|
||||
frappe.set_user('Administrator')
|
||||
|
||||
def test_user_type(self):
|
||||
new_user = frappe.get_doc(dict(doctype='User', email='test-for-type@example.com',
|
||||
|
|
@ -106,13 +107,17 @@ class TestUser(unittest.TestCase):
|
|||
frappe.set_user("testperm@example.com")
|
||||
|
||||
me = frappe.get_doc("User", "testperm@example.com")
|
||||
self.assertRaises(frappe.PermissionError, me.add_roles, "System Manager")
|
||||
me.add_roles("System Manager")
|
||||
|
||||
# system manager is not added (it is reset)
|
||||
self.assertFalse('System Manager' in [d.role for d in me.roles])
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
me = frappe.get_doc("User", "testperm@example.com")
|
||||
me.add_roles("System Manager")
|
||||
|
||||
# system manager now added by Administrator
|
||||
self.assertTrue("System Manager" in [d.role for d in me.get("roles")])
|
||||
|
||||
# def test_deny_multiple_sessions(self):
|
||||
|
|
@ -235,6 +240,29 @@ class TestUser(unittest.TestCase):
|
|||
|
||||
self.assertRaises(frappe.ValidationError, user.reset_password, False)
|
||||
|
||||
def test_user_rollback(self):
|
||||
""" """
|
||||
frappe.db.commit()
|
||||
frappe.db.begin()
|
||||
user_id = str(uuid.uuid4())
|
||||
email = f'{user_id}@example.com'
|
||||
try:
|
||||
frappe.flags.in_import = True # disable throttling
|
||||
frappe.get_doc(dict(
|
||||
doctype='User',
|
||||
email=email,
|
||||
first_name=user_id,
|
||||
)).insert()
|
||||
finally:
|
||||
frappe.flags.in_import = False
|
||||
|
||||
# Check user has been added
|
||||
self.assertIsNotNone(frappe.db.get("User", {"email": email}))
|
||||
|
||||
# Check that rollback works
|
||||
frappe.db.rollback()
|
||||
self.assertIsNone(frappe.db.get("User", {"email": email}))
|
||||
|
||||
|
||||
def delete_contact(user):
|
||||
frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user)
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
.user-role {
|
||||
padding: 5px;
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
table.user-perm {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
overflow-x: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
}
|
||||
|
||||
table.user-perm td, table.user-perm th {
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #aaa;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.module-block-list .checkbox {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.module-block-list .checkbox label {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ frappe.ui.form.on('User', {
|
|||
},
|
||||
callback: function(data) {
|
||||
frm.set_value("roles", []);
|
||||
$.each(data.message || [], function(i, v){
|
||||
$.each(data.message || [], function(i, v) {
|
||||
var d = frm.add_child("roles");
|
||||
d.role = v.role;
|
||||
});
|
||||
|
|
@ -37,16 +37,35 @@ 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();
|
||||
|
||||
if(frm.can_edit_roles && !frm.is_new()) {
|
||||
if(!frm.roles_editor) {
|
||||
var role_area = $('<div style="min-height: 300px">')
|
||||
if (frm.can_edit_roles && !frm.is_new()) {
|
||||
if (!frm.roles_editor) {
|
||||
const role_area = $('<div class="role-editor">')
|
||||
.appendTo(frm.fields_dict.roles_html.wrapper);
|
||||
frm.roles_editor = new frappe.RoleEditor(role_area, frm, frm.doc.role_profile_name ? 1 : 0);
|
||||
|
||||
var module_area = $('<div style="min-height: 300px">')
|
||||
var module_area = $('<div>')
|
||||
.appendTo(frm.fields_dict.modules_html.wrapper);
|
||||
frm.module_editor = new frappe.ModuleEditor(frm, module_area);
|
||||
} else {
|
||||
|
|
@ -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-6"><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});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,20 +7,20 @@
|
|||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"sb0_5",
|
||||
"enabled",
|
||||
"section_break_3",
|
||||
"email",
|
||||
"first_name",
|
||||
"middle_name",
|
||||
"last_name",
|
||||
"language",
|
||||
"column_break0",
|
||||
"first_name",
|
||||
"full_name",
|
||||
"time_zone",
|
||||
"column_break_11",
|
||||
"middle_name",
|
||||
"username",
|
||||
"send_welcome_email",
|
||||
"unsubscribed",
|
||||
"column_break0",
|
||||
"username",
|
||||
"language",
|
||||
"time_zone",
|
||||
"user_image",
|
||||
"sb1",
|
||||
"role_profile_name",
|
||||
|
|
@ -28,15 +28,17 @@
|
|||
"roles",
|
||||
"short_bio",
|
||||
"gender",
|
||||
"phone",
|
||||
"mobile_no",
|
||||
"birth_date",
|
||||
"location",
|
||||
"banner_image",
|
||||
"column_break_22",
|
||||
"interest",
|
||||
"banner_image",
|
||||
"desk_theme",
|
||||
"column_break_26",
|
||||
"phone",
|
||||
"location",
|
||||
"bio",
|
||||
"mute_sounds",
|
||||
"column_break_22",
|
||||
"mobile_no",
|
||||
"change_password",
|
||||
"new_password",
|
||||
"logout_all_sessions",
|
||||
|
|
@ -47,13 +49,13 @@
|
|||
"document_follow_notify",
|
||||
"document_follow_frequency",
|
||||
"email_settings",
|
||||
"email_signature",
|
||||
"thread_notify",
|
||||
"send_me_a_copy",
|
||||
"allowed_in_mentions",
|
||||
"email_signature",
|
||||
"email_inbox",
|
||||
"user_emails",
|
||||
"sb_allow_modules",
|
||||
"module_profile",
|
||||
"modules_html",
|
||||
"block_modules",
|
||||
"home_settings",
|
||||
|
|
@ -61,15 +63,16 @@
|
|||
"defaults",
|
||||
"sb3",
|
||||
"simultaneous_sessions",
|
||||
"user_type",
|
||||
"login_after",
|
||||
"login_before",
|
||||
"restrict_ip",
|
||||
"bypass_restrict_ip_check_if_2fa_enabled",
|
||||
"column_break1",
|
||||
"last_login",
|
||||
"last_ip",
|
||||
"column_break1",
|
||||
"login_after",
|
||||
"user_type",
|
||||
"last_active",
|
||||
"section_break_63",
|
||||
"login_before",
|
||||
"bypass_restrict_ip_check_if_2fa_enabled",
|
||||
"last_login",
|
||||
"last_known_versions",
|
||||
"third_party_authentication",
|
||||
"social_logins",
|
||||
|
|
@ -80,10 +83,6 @@
|
|||
"api_secret"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "sb0_5",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enabled",
|
||||
|
|
@ -96,7 +95,8 @@
|
|||
{
|
||||
"depends_on": "enabled",
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Basic Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "email",
|
||||
|
|
@ -302,7 +302,7 @@
|
|||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"fieldname": "logout_all_sessions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Logout From All Devices After Changing Password"
|
||||
|
|
@ -577,6 +577,30 @@
|
|||
"fieldtype": "Password",
|
||||
"label": "API Secret",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_26",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_63",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "desk_theme",
|
||||
"fieldtype": "Select",
|
||||
"label": "Desk Theme",
|
||||
"options": "Light\nDark"
|
||||
},
|
||||
{
|
||||
"fieldname": "module_profile",
|
||||
"fieldtype": "Link",
|
||||
"label": "Module Profile",
|
||||
"options": "Module Profile"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
|
|
@ -623,11 +647,6 @@
|
|||
"link_doctype": "User Permission",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Settings",
|
||||
"link_doctype": "Assignment Rule",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Settings",
|
||||
"link_doctype": "Document Follow",
|
||||
|
|
@ -650,7 +669,7 @@
|
|||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-10-18 15:18:53.126800",
|
||||
"modified": "2021-02-01 16:11:06.037543",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
@ -678,10 +697,11 @@
|
|||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"route": "user",
|
||||
"search_fields": "full_name",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "full_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
@ -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...":
|
||||
|
|
@ -85,9 +86,18 @@ class User(Document):
|
|||
|
||||
def validate_roles(self):
|
||||
if self.role_profile_name:
|
||||
role_profile = frappe.get_doc('Role Profile', self.role_profile_name)
|
||||
self.set('roles', [])
|
||||
self.append_roles(*[role.role for role in role_profile.roles])
|
||||
role_profile = frappe.get_doc('Role Profile', self.role_profile_name)
|
||||
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:
|
||||
|
|
@ -108,7 +118,7 @@ class User(Document):
|
|||
)
|
||||
if self.name not in ('Administrator', 'Guest') and not self.user_image:
|
||||
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)
|
||||
|
|
@ -187,20 +197,17 @@ class User(Document):
|
|||
|
||||
|
||||
def share_with_self(self):
|
||||
if self.user_type=="System User":
|
||||
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
|
||||
flags={"ignore_share_permission": True})
|
||||
else:
|
||||
frappe.share.remove(self.doctype, self.name, self.name,
|
||||
flags={"ignore_share_permission": True, "ignore_permissions": True})
|
||||
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
|
||||
flags={"ignore_share_permission": True})
|
||||
|
||||
def validate_share(self, docshare):
|
||||
if docshare.user == self.name:
|
||||
if self.user_type=="System User":
|
||||
if docshare.share != 1:
|
||||
frappe.throw(_("Sorry! User should have complete access to their own record."))
|
||||
else:
|
||||
frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
|
||||
pass
|
||||
# if docshare.user == self.name:
|
||||
# if self.user_type=="System User":
|
||||
# if docshare.share != 1:
|
||||
# frappe.throw(_("Sorry! User should have complete access to their own record."))
|
||||
# else:
|
||||
# frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
|
||||
|
||||
def send_password_notification(self, new_password):
|
||||
try:
|
||||
|
|
@ -292,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)
|
||||
|
|
@ -555,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)
|
||||
|
||||
|
|
@ -585,7 +596,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
|
|||
frappe.db.set_value("User", user, "reset_password_key", "")
|
||||
|
||||
if user_doc.user_type == "System User":
|
||||
return "/desk"
|
||||
return "/app"
|
||||
else:
|
||||
return redirect_url if redirect_url else "/"
|
||||
|
||||
|
|
@ -1006,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
|
||||
|
||||
|
|
@ -1042,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)
|
||||
|
|
@ -1102,7 +1123,6 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):
|
|||
|
||||
contact.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_keys(user):
|
||||
"""
|
||||
|
|
@ -1123,6 +1143,11 @@ def generate_keys(user):
|
|||
return {"api_secret": api_secret}
|
||||
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)
|
||||
|
||||
@frappe.whitelist()
|
||||
def switch_theme(theme):
|
||||
if theme in ["Dark", "Light"]:
|
||||
frappe.db.set_value("User", frappe.session.user, "desk_theme", theme)
|
||||
|
||||
def update_password_reset_limit(user):
|
||||
generated_link_count = get_generated_link_count(user)
|
||||
generated_link_count += 1
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue