Merge branch 'develop' of github.com:frappe/frappe into skip-backup-tables
This commit is contained in:
commit
bf11c70b46
260 changed files with 6656 additions and 3610 deletions
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -9,7 +9,7 @@ Welcome to the Frappe Framework issue tracker! Before creating an issue, please
|
|||
|
||||
1. This tracker should only be used to report bugs and request features / enhancements to Frappe
|
||||
- For questions and general support, use https://stackoverflow.com/questions/tagged/frappe
|
||||
- For documentation issues, refer to https://frappe.io/docs/user/en or the developer cheetsheet https://github.com/frappe/frappe/wiki/Developer-Cheatsheet
|
||||
- For documentation issues, refer to https://frappeframework.com/docs/user/en or the developer cheetsheet https://github.com/frappe/frappe/wiki/Developer-Cheatsheet
|
||||
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
|
||||
the original discussion.
|
||||
3. When making a bug report, make sure you provide all required information. The easier it is for
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -9,7 +9,7 @@ Welcome to the Frappe Framework issue tracker! Before creating an issue, please
|
|||
|
||||
1. This tracker should only be used to report bugs and request features / enhancements to Frappe
|
||||
- For questions and general support, refer to https://stackoverflow.com/questions/tagged/frappe
|
||||
- For documentation issues, use https://frappe.io/docs/user/en or the developer cheetsheet https://github.com/frappe/frappe/wiki/Developer-Cheatsheet
|
||||
- For documentation issues, use https://frappeframework.com/docs/user/en or the developer cheetsheet https://frappeframework.com/docs/user/en/bench/resources/bench-commands-cheatsheet
|
||||
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
|
||||
the original discussion.
|
||||
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ for questions about using `ERPNext`: https://discuss.erpnext.com
|
|||
|
||||
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)
|
||||
|
||||
For documentation issues, use the [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
|
||||
For documentation issues, use the [Frappe Framework Documentation](https://frappeframework.com/docs) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
|
||||
|
||||
For a slightly outdated yet informative developer guide: https://www.youtube.com/playlist?list=PL3lFfCEoMxvzHtsZHFJ4T3n5yMM3nGJ1W
|
||||
|
||||
|
|
|
|||
7
.github/frappe-framework-logo.svg
vendored
7
.github/frappe-framework-logo.svg
vendored
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.9 KiB |
2
.github/frappe_linter/translation.py
vendored
2
.github/frappe_linter/translation.py
vendored
|
|
@ -22,7 +22,7 @@ for _file in files:
|
|||
print(f'A syntax error has been discovered at line number: {num}')
|
||||
print(f'Syntax error occurred with: {line}')
|
||||
if errors_encounter > 0:
|
||||
print('You can visit "https://frappe.io/docs/user/en/translations" to resolve this error.')
|
||||
print('You can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.')
|
||||
assert 1+1 == 3
|
||||
else:
|
||||
print('Good To Go!')
|
||||
|
|
|
|||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -188,4 +188,7 @@ typings/
|
|||
|
||||
# cypress
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
cypress/videos
|
||||
|
||||
# JetBrains IDEs
|
||||
.idea/
|
||||
|
|
|
|||
4
.snyk
4
.snyk
|
|
@ -1,5 +1,5 @@
|
|||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.14.1
|
||||
version: v1.19.0
|
||||
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||
ignore:
|
||||
SNYK-JS-AWESOMPLETE-174474:
|
||||
|
|
@ -63,3 +63,5 @@ patch:
|
|||
patched: '2020-04-30T23:02:32.330Z'
|
||||
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-04-30T23:02:32.330Z'
|
||||
- quill-image-resize > lodash:
|
||||
patched: '2020-08-24T23:06:37.710Z'
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ matrix:
|
|||
- name: "Python 3.7 MariaDB"
|
||||
python: 3.7
|
||||
env: DB=mariadb TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
|
||||
- name: "Python 3.7 PostgreSQL"
|
||||
python: 3.7
|
||||
env: DB=postgres TYPE=server
|
||||
script: bench --site test_site run-tests --coverage
|
||||
script: bench --verbose --site test_site run-tests --coverage
|
||||
|
||||
- name: "Cypress"
|
||||
python: 3.7
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"db_host": "localhost",
|
||||
"db_name": "test_frappe_consumer",
|
||||
"db_password": "test_frappe",
|
||||
"allow_tests": true,
|
||||
"db_type": "mariadb",
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"db_name": "test_frappe_consumer",
|
||||
"db_password": "test_frappe",
|
||||
"db_type": "postgres",
|
||||
"allow_tests": true,
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
"mail_login": "test@example.com",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"db_host": "localhost",
|
||||
"db_name": "test_frappe_producer",
|
||||
"db_password": "test_frappe",
|
||||
"allow_tests": true,
|
||||
"db_type": "mariadb",
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"db_name": "test_frappe_producer",
|
||||
"db_password": "test_frappe",
|
||||
"db_type": "postgres",
|
||||
"allow_tests": true,
|
||||
"auto_email_id": "test@example.com",
|
||||
"mail_server": "smtp.example.com",
|
||||
"mail_login": "test@example.com",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
<a href="https://travis-ci.com/frappe/frappe">
|
||||
<img src="https://travis-ci.com/frappe/frappe.svg?branch=develop">
|
||||
</a>
|
||||
<a href='https://frappe.io/docs'>
|
||||
<a href='https://frappeframework.com/docs'>
|
||||
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
|
||||
</a>
|
||||
<a href='https://www.codetriage.com/frappe/frappe'>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ context('Recorder', () => {
|
|||
|
||||
cy.visit('/desk#recorder');
|
||||
|
||||
cy.contains('.list-row-container span', 'frappe.desk.reportview.get').click();
|
||||
cy.get('.list-row-container span').contains('frappe.desk.reportview.get').click();
|
||||
|
||||
cy.location('hash').should('contain', '#recorder/request/');
|
||||
cy.get('form').should('contain', 'frappe.desk.reportview.get');
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ def init(site, sites_path=None, new_site=False):
|
|||
local.site = site
|
||||
local.sites_path = sites_path
|
||||
local.site_path = os.path.join(sites_path, site)
|
||||
local.all_apps = None
|
||||
|
||||
local.request_ip = None
|
||||
local.response = _dict({"docs":[]})
|
||||
|
|
@ -231,8 +232,7 @@ def get_site_config(sites_path=None, site_path=None):
|
|||
if os.path.exists(site_config):
|
||||
config.update(get_file_json(site_config))
|
||||
elif local.site and not local.flags.new_site:
|
||||
print("Site {0} does not exist".format(local.site))
|
||||
sys.exit(1)
|
||||
raise IncorrectSitePath("{0} does not exist".format(local.site))
|
||||
|
||||
return _dict(config)
|
||||
|
||||
|
|
@ -267,7 +267,7 @@ def destroy():
|
|||
# memcache
|
||||
redis_server = None
|
||||
def cache():
|
||||
"""Returns memcache connection."""
|
||||
"""Returns redis connection."""
|
||||
global redis_server
|
||||
if not redis_server:
|
||||
from frappe.utils.redis_wrapper import RedisWrapper
|
||||
|
|
@ -290,6 +290,9 @@ def errprint(msg):
|
|||
|
||||
error_log.append({"exc": msg})
|
||||
|
||||
def print_sql(enable=True):
|
||||
return cache().set_value('flag_print_sql', enable)
|
||||
|
||||
def log(msg):
|
||||
"""Add to `debug_log`.
|
||||
|
||||
|
|
@ -300,7 +303,7 @@ def log(msg):
|
|||
|
||||
debug_log.append(as_unicode(msg))
|
||||
|
||||
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None, is_minimizable=None):
|
||||
def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None, alert=False, primary_action=None, is_minimizable=None, wide=None):
|
||||
"""Print a message to the user (via HTTP response).
|
||||
Messages are sent in the `__server_messages` property in the
|
||||
response JSON and shown in a pop-up / modal.
|
||||
|
|
@ -310,6 +313,8 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
|
|||
:param raise_exception: [optional] Raise given exception and show message.
|
||||
:param as_table: [optional] If `msg` is a list of lists, render as HTML table.
|
||||
:param primary_action: [optional] Bind a primary server/client side action.
|
||||
:param is_minimizable: [optional] Allow users to minimize the modal
|
||||
:param wide: [optional] Show wide modal
|
||||
"""
|
||||
from frappe.utils import encode
|
||||
|
||||
|
|
@ -367,6 +372,9 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
|
|||
if primary_action:
|
||||
out.primary_action = primary_action
|
||||
|
||||
if wide:
|
||||
out.wide = wide
|
||||
|
||||
message_log.append(json.dumps(out))
|
||||
|
||||
if raise_exception and hasattr(raise_exception, '__name__'):
|
||||
|
|
@ -388,12 +396,12 @@ def clear_last_message():
|
|||
if len(local.message_log) > 0:
|
||||
local.message_log = local.message_log[:-1]
|
||||
|
||||
def throw(msg, exc=ValidationError, title=None, is_minimizable=None):
|
||||
def throw(msg, exc=ValidationError, title=None, is_minimizable=None, wide=None):
|
||||
"""Throw execption and show message (`msgprint`).
|
||||
|
||||
:param msg: Message.
|
||||
:param exc: Exception class. Default `frappe.ValidationError`"""
|
||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable)
|
||||
msgprint(msg, raise_exception=exc, title=title, indicator='red', is_minimizable=is_minimizable, wide=wide)
|
||||
|
||||
def emit_js(js, user=False, **kwargs):
|
||||
if user == False:
|
||||
|
|
@ -436,12 +444,8 @@ def get_roles(username=None):
|
|||
"""Returns roles of current user."""
|
||||
if not local.session:
|
||||
return ["Guest"]
|
||||
|
||||
if username:
|
||||
import frappe.permissions
|
||||
return frappe.permissions.get_roles(username)
|
||||
else:
|
||||
return get_user().get_roles()
|
||||
import frappe.permissions
|
||||
return frappe.permissions.get_roles(username or local.session.user)
|
||||
|
||||
def get_request_header(key, default=None):
|
||||
"""Return HTTP request header.
|
||||
|
|
@ -762,7 +766,7 @@ def get_doc(*args, **kwargs):
|
|||
|
||||
# insert a new document
|
||||
todo = frappe.get_doc({"doctype":"ToDo", "description": "test"})
|
||||
tood.insert()
|
||||
todo.insert()
|
||||
|
||||
# open an existing document
|
||||
todo = frappe.get_doc("ToDo", "TD0001")
|
||||
|
|
@ -921,10 +925,13 @@ def get_installed_apps(sort=False, frappe_last=False):
|
|||
if not db:
|
||||
connect()
|
||||
|
||||
if not local.all_apps:
|
||||
local.all_apps = get_all_apps(True)
|
||||
|
||||
installed = json.loads(db.get_global("installed_apps") or "[]")
|
||||
|
||||
if sort:
|
||||
installed = [app for app in get_all_apps(True) if app in installed]
|
||||
installed = [app for app in local.all_apps if app in installed]
|
||||
|
||||
if frappe_last:
|
||||
if 'frappe' in installed:
|
||||
|
|
@ -1559,10 +1566,10 @@ def get_doctype_app(doctype):
|
|||
|
||||
loggers = {}
|
||||
log_level = None
|
||||
def logger(module=None, with_more_info=False):
|
||||
def logger(module=None, with_more_info=False, allow_site=True, filter=None, max_size=100_000, file_count=20):
|
||||
'''Returns a python logger that uses StreamHandler'''
|
||||
from frappe.utils.logger import get_logger
|
||||
return get_logger(module=module, with_more_info=with_more_info)
|
||||
return get_logger(module=module, with_more_info=with_more_info, allow_site=allow_site, filter=filter, max_size=max_size, file_count=file_count)
|
||||
|
||||
def log_error(message=None, title=_("Error")):
|
||||
'''Log error to Error Log'''
|
||||
|
|
@ -1707,3 +1714,7 @@ def mock(type, size=1, locale='en'):
|
|||
|
||||
from frappe.chat.util import squashify
|
||||
return squashify(results)
|
||||
|
||||
def validate_and_sanitize_search_inputs(fn):
|
||||
from frappe.desk.search import validate_and_sanitize_search_inputs as func
|
||||
return func(fn)
|
||||
|
|
|
|||
|
|
@ -99,15 +99,16 @@ def application(request):
|
|||
frappe.monitor.stop(response)
|
||||
frappe.recorder.dump()
|
||||
|
||||
frappe.logger("frappe.web").info({
|
||||
"site": get_site_name(request.host),
|
||||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"),
|
||||
"base_url": getattr(request, "base_url", "NOTFOUND"),
|
||||
"full_path": getattr(request, "full_path", "NOTFOUND"),
|
||||
"method": getattr(request, "method", "NOTFOUND"),
|
||||
"scheme": getattr(request, "scheme", "NOTFOUND"),
|
||||
"http_status_code": getattr(response, "status_code", "NOTFOUND")
|
||||
})
|
||||
if hasattr(frappe.local, 'conf') and frappe.local.conf.enable_frappe_logger:
|
||||
frappe.logger("frappe.web", allow_site=frappe.local.site).info({
|
||||
"site": get_site_name(request.host),
|
||||
"remote_addr": getattr(request, "remote_addr", "NOTFOUND"),
|
||||
"base_url": getattr(request, "base_url", "NOTFOUND"),
|
||||
"full_path": getattr(request, "full_path", "NOTFOUND"),
|
||||
"method": getattr(request, "method", "NOTFOUND"),
|
||||
"scheme": getattr(request, "scheme", "NOTFOUND"),
|
||||
"http_status_code": getattr(response, "status_code", "NOTFOUND")
|
||||
})
|
||||
|
||||
if response and hasattr(frappe.local, 'rate_limiter'):
|
||||
response.headers.extend(frappe.local.rate_limiter.headers())
|
||||
|
|
@ -256,9 +257,11 @@ def serve(port=8000, profile=False, no_reload=False, no_threading=False, site=No
|
|||
'SERVER_NAME': 'localhost:8000'
|
||||
}
|
||||
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.propagate = False
|
||||
|
||||
in_test_env = os.environ.get('CI')
|
||||
if in_test_env:
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
|
||||
run_simple('0.0.0.0', int(port), application,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ from frappe.utils.password import check_password, delete_login_failed_cache
|
|||
from frappe.core.doctype.activity_log.activity_log import add_authentication_log
|
||||
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor,
|
||||
confirm_otp_token, get_cached_user_pass)
|
||||
from frappe.website.utils import get_home_page
|
||||
|
||||
from six.moves.urllib.parse import quote
|
||||
|
||||
|
|
@ -167,7 +168,7 @@ class LoginManager:
|
|||
frappe.local.cookie_manager.set_cookie("system_user", "no")
|
||||
if not resume:
|
||||
frappe.local.response["message"] = "No App"
|
||||
frappe.local.response["home_page"] = get_website_user_home_page(self.user)
|
||||
frappe.local.response["home_page"] = '/' + get_home_page()
|
||||
else:
|
||||
frappe.local.cookie_manager.set_cookie("system_user", "yes")
|
||||
if not resume:
|
||||
|
|
@ -338,8 +339,13 @@ class CookieManager:
|
|||
self.set_cookie("country", frappe.session.session_country)
|
||||
|
||||
def set_cookie(self, key, value, expires=None, secure=False, httponly=False, samesite="Lax"):
|
||||
if not secure:
|
||||
if not secure and hasattr(frappe.local, 'request'):
|
||||
secure = frappe.local.request.scheme == "https"
|
||||
|
||||
# Cordova does not work with Lax
|
||||
if frappe.local.session.data.device == "mobile":
|
||||
samesite = None
|
||||
|
||||
self.cookies[key] = {
|
||||
"value": value,
|
||||
"expires": expires,
|
||||
|
|
@ -377,16 +383,6 @@ def clear_cookies():
|
|||
frappe.session.sid = ""
|
||||
frappe.local.cookie_manager.delete_cookie(["full_name", "user_id", "sid", "user_image", "system_user"])
|
||||
|
||||
def get_website_user_home_page(user):
|
||||
home_page_method = frappe.get_hooks('get_website_user_home_page')
|
||||
if home_page_method:
|
||||
home_page = frappe.get_attr(home_page_method[-1])(user)
|
||||
return '/' + home_page.strip('/')
|
||||
elif frappe.get_hooks('website_user_home_page'):
|
||||
return '/' + frappe.get_hooks('website_user_home_page')[-1].strip('/')
|
||||
else:
|
||||
return '/me'
|
||||
|
||||
def get_last_tried_login_data(user, get_last_login=False):
|
||||
locked_account_time = frappe.cache().hget('locked_account_time', user)
|
||||
if get_last_login and locked_account_time:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"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 \"label\": \"Video\",\n \"name\": \"Video\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
|
||||
"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,
|
||||
|
|
@ -29,10 +29,11 @@
|
|||
"docstatus": 0,
|
||||
"doctype": "Desk Page",
|
||||
"extends_another_page": 0,
|
||||
"hide_custom": 0,
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Tools",
|
||||
"modified": "2020-04-20 18:21:14.152537",
|
||||
"modified": "2020-07-21 19:32:18.480700",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Automation",
|
||||
"name": "Tools",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from frappe.model.document import Document
|
|||
from frappe.desk.form import assign_to
|
||||
import frappe.cache_manager
|
||||
from frappe import _
|
||||
from frappe.model import log_types
|
||||
|
||||
class AssignmentRule(Document):
|
||||
|
||||
|
|
@ -19,10 +20,13 @@ class AssignmentRule(Document):
|
|||
frappe.throw(_("Assignment Day {0} has been repeated.").format(frappe.bold(repeated_days)))
|
||||
|
||||
def on_update(self): # pylint: disable=no-self-use
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.name)
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.document_type)
|
||||
|
||||
def after_rename(self, old, new, merge): # pylint: disable=no-self-use
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.name)
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.document_type)
|
||||
|
||||
def on_trash(self): # pylint: disable=no-self-use
|
||||
frappe.cache_manager.clear_doctype_map('Assignment Rule', self.document_type)
|
||||
|
||||
def apply_unassign(self, doc, assignments):
|
||||
if (self.unassign_condition and
|
||||
|
|
@ -165,7 +169,13 @@ def reopen_closed_assignment(doc):
|
|||
return True
|
||||
|
||||
def apply(doc, method=None, doctype=None, name=None):
|
||||
if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard:
|
||||
if not doctype:
|
||||
doctype = doc.doctype
|
||||
|
||||
if (frappe.flags.in_patch
|
||||
or frappe.flags.in_install
|
||||
or frappe.flags.in_setup_wizard
|
||||
or doctype in log_types):
|
||||
return
|
||||
|
||||
if not doc and doctype and name:
|
||||
|
|
|
|||
|
|
@ -374,6 +374,7 @@ def make_auto_repeat(doctype, docname, frequency = 'Daily', start_date = None, e
|
|||
|
||||
# method for reference_doctype filter
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_auto_repeat_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
res = frappe.db.get_all('Property Setter', {
|
||||
'property': 'allow_auto_repeat',
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ from __future__ import unicode_literals
|
|||
import frappe
|
||||
from frappe.model.document import Document
|
||||
import frappe.cache_manager
|
||||
from frappe.model import log_types
|
||||
|
||||
class MilestoneTracker(Document):
|
||||
def on_update(self):
|
||||
frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.name)
|
||||
frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.document_type)
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.name)
|
||||
frappe.cache_manager.clear_doctype_map('Milestone Tracker', self.document_type)
|
||||
|
||||
def apply(self, doc):
|
||||
before_save = doc.get_doc_before_save()
|
||||
|
|
@ -32,8 +33,15 @@ class MilestoneTracker(Document):
|
|||
def evaluate_milestone(doc, event):
|
||||
if (frappe.flags.in_install
|
||||
or frappe.flags.in_migrate
|
||||
or frappe.flags.in_setup_wizard):
|
||||
or frappe.flags.in_setup_wizard
|
||||
or doc.doctype in log_types):
|
||||
return
|
||||
for d in frappe.cache_manager.get_doctype_map('Milestone Tracker', doc.doctype,
|
||||
dict(document_type = doc.doctype, disabled=0)):
|
||||
frappe.get_doc('Milestone Tracker', d.name).apply(doc)
|
||||
|
||||
# track milestones related to this doctype
|
||||
for d in get_milestone_trackers(doc.doctype):
|
||||
frappe.get_doc('Milestone Tracker', d.get('name')).apply(doc)
|
||||
|
||||
def get_milestone_trackers(doctype):
|
||||
return frappe.cache_manager.get_doctype_map('Milestone Tracker', doctype,
|
||||
dict(document_type = doctype, disabled=0))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,16 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import frappe.cache_manager
|
||||
import unittest
|
||||
|
||||
class TestMilestoneTracker(unittest.TestCase):
|
||||
def test_milestone(self):
|
||||
frappe.db.sql('delete from `tabMilestone Tracker`')
|
||||
frappe.get_doc(dict(
|
||||
|
||||
frappe.cache().delete_key('milestone_tracker_map')
|
||||
|
||||
milestone_tracker = frappe.get_doc(dict(
|
||||
doctype = 'Milestone Tracker',
|
||||
document_type = 'ToDo',
|
||||
track_field = 'status'
|
||||
|
|
@ -17,7 +21,8 @@ class TestMilestoneTracker(unittest.TestCase):
|
|||
|
||||
todo = frappe.get_doc(dict(
|
||||
doctype = 'ToDo',
|
||||
description = 'test milestone'
|
||||
description = 'test milestone',
|
||||
status = 'Open'
|
||||
)).insert()
|
||||
|
||||
milestones = frappe.get_all('Milestone',
|
||||
|
|
@ -40,3 +45,6 @@ class TestMilestoneTracker(unittest.TestCase):
|
|||
self.assertEqual(milestones[0].track_field, 'status')
|
||||
self.assertEqual(milestones[0].value, 'Closed')
|
||||
|
||||
# cleanup
|
||||
frappe.db.sql('delete from tabMilestone')
|
||||
milestone_tracker.delete()
|
||||
|
|
@ -21,6 +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
|
||||
|
||||
def get_bootinfo():
|
||||
"""build and return boot info"""
|
||||
|
|
@ -59,6 +60,7 @@ def get_bootinfo():
|
|||
load_print(bootinfo, doclist)
|
||||
doclist.extend(get_meta_bundle("Page"))
|
||||
bootinfo.home_folder = frappe.db.get_value("File", {"is_home_folder": 1})
|
||||
bootinfo.navbar_settings = get_navbar_settings()
|
||||
|
||||
# ipinfo
|
||||
if frappe.session.data.get('ipinfo'):
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ from frappe.desk.notifications import (delete_notification_count_for,
|
|||
|
||||
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",
|
||||
"app_modules", "module_app", "system_settings",
|
||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
|
||||
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
|
||||
'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts',
|
||||
'sitemap_routes', 'db_tables')
|
||||
'sitemap_routes', 'db_tables') + doctype_map_keys
|
||||
|
||||
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
||||
"defaults", "user_permissions", "home_page", "linked_with",
|
||||
|
|
@ -24,8 +27,8 @@ user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
|
|||
"has_role:Page", "has_role:Report", "desk_sidebar_items")
|
||||
|
||||
doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified",
|
||||
"linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map', 'data_import_column_header_map')
|
||||
|
||||
"linked_doctypes", 'notifications', 'workflow' ,
|
||||
'data_import_column_header_map') + doctype_map_keys
|
||||
|
||||
def clear_user_cache(user=None):
|
||||
cache = frappe.cache()
|
||||
|
|
@ -102,19 +105,19 @@ def clear_doctype_cache(doctype=None):
|
|||
# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
|
||||
clear_document_cache()
|
||||
|
||||
def get_doctype_map(doctype, name, filters, order_by=None):
|
||||
def get_doctype_map(doctype, name, filters=None, order_by=None):
|
||||
cache = frappe.cache()
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
doctype_map = cache.hget(cache_key, name)
|
||||
|
||||
if doctype_map:
|
||||
if doctype_map is not None:
|
||||
# cached, return
|
||||
items = json.loads(doctype_map)
|
||||
else:
|
||||
# non cached, build cache
|
||||
try:
|
||||
items = frappe.get_all(doctype, filters=filters, order_by = order_by)
|
||||
cache.hset(cache_key, doctype, json.dumps(items))
|
||||
cache.hset(cache_key, name, json.dumps(items))
|
||||
except frappe.db.TableMissingError:
|
||||
# executed from inside patch, ignore
|
||||
items = []
|
||||
|
|
@ -122,8 +125,7 @@ def get_doctype_map(doctype, name, filters, order_by=None):
|
|||
return items
|
||||
|
||||
def clear_doctype_map(doctype, name):
|
||||
cache_key = frappe.scrub(doctype) + '_map'
|
||||
frappe.cache().hdel(cache_key, name)
|
||||
frappe.cache().hdel(frappe.scrub(doctype) + '_map', name)
|
||||
|
||||
def build_table_count_cache():
|
||||
if (frappe.flags.in_patch
|
||||
|
|
|
|||
|
|
@ -73,19 +73,32 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
|
|||
frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError)
|
||||
|
||||
filters = get_safe_filters(filters)
|
||||
if isinstance(filters, string_types):
|
||||
filters = {"name": filters}
|
||||
|
||||
try:
|
||||
fieldname = json.loads(fieldname)
|
||||
fields = json.loads(fieldname)
|
||||
except (TypeError, ValueError):
|
||||
# name passed, not json
|
||||
pass
|
||||
fields = [fieldname]
|
||||
|
||||
# check whether the used filters were really parseable and usable
|
||||
# and did not just result in an empty string or dict
|
||||
if not filters:
|
||||
filters = None
|
||||
|
||||
return frappe.db.get_value(doctype, filters, fieldname, as_dict=as_dict, debug=debug)
|
||||
|
||||
if frappe.get_meta(doctype).issingle:
|
||||
value = frappe.db.get_values_from_single(fields, filters, doctype, as_dict=as_dict, debug=debug)
|
||||
else:
|
||||
value = frappe.get_list(doctype, filters=filters, fields=fields, debug=debug, limit=1)
|
||||
|
||||
if as_dict:
|
||||
value = value[0] if value else {}
|
||||
else:
|
||||
value = value[0].fieldname
|
||||
|
||||
return value
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_single_value(doctype, field):
|
||||
|
|
|
|||
|
|
@ -274,8 +274,9 @@ def disable_user(context, email):
|
|||
@click.command('migrate')
|
||||
@click.option('--rebuild-website', help="Rebuild webpages after migration")
|
||||
@click.option('--skip-failing', is_flag=True, help="Skip patches that fail to run")
|
||||
@click.option('--skip-search-index', is_flag=True, help="Skip search indexing for web documents")
|
||||
@pass_context
|
||||
def migrate(context, rebuild_website=False, skip_failing=False):
|
||||
def migrate(context, rebuild_website=False, skip_failing=False, skip_search_index=False):
|
||||
"Run patches, sync schema and rebuild files/translations"
|
||||
from frappe.migrate import migrate
|
||||
|
||||
|
|
@ -284,13 +285,18 @@ def migrate(context, rebuild_website=False, skip_failing=False):
|
|||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
try:
|
||||
migrate(context.verbose, rebuild_website=rebuild_website, skip_failing=skip_failing)
|
||||
migrate(
|
||||
context.verbose,
|
||||
rebuild_website=rebuild_website,
|
||||
skip_failing=skip_failing,
|
||||
skip_search_index=skip_search_index
|
||||
)
|
||||
finally:
|
||||
frappe.destroy()
|
||||
if not context.sites:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
print("Compiling Python Files...")
|
||||
print("Compiling Python files...")
|
||||
compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*'))
|
||||
|
||||
@click.command('migrate-to')
|
||||
|
|
@ -655,6 +661,22 @@ def start_ngrok(context):
|
|||
frappe.destroy()
|
||||
ngrok.kill()
|
||||
|
||||
@click.command('build-search-index')
|
||||
@pass_context
|
||||
def build_search_index(context):
|
||||
from frappe.search.website_search import build_index_for_all_routes
|
||||
site = get_site(context)
|
||||
if not site:
|
||||
raise SiteNotSpecifiedError
|
||||
|
||||
print('Building search index for {}'.format(site))
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
try:
|
||||
build_index_for_all_routes()
|
||||
finally:
|
||||
frappe.destroy()
|
||||
|
||||
commands = [
|
||||
add_system_manager,
|
||||
backup,
|
||||
|
|
@ -680,5 +702,6 @@ commands = [
|
|||
start_recording,
|
||||
stop_recording,
|
||||
add_to_hosts,
|
||||
start_ngrok
|
||||
start_ngrok,
|
||||
build_search_index
|
||||
]
|
||||
|
|
|
|||
|
|
@ -528,7 +528,6 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
|
|||
@pass_context
|
||||
def run_ui_tests(context, app, headless=False):
|
||||
"Run UI tests"
|
||||
|
||||
site = get_site(context)
|
||||
app_base_path = os.path.abspath(os.path.join(frappe.get_app_path(app), '..'))
|
||||
site_url = frappe.utils.get_site_url(site)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ def get_data():
|
|||
"name": "Stripe Settings",
|
||||
"description": _("Stripe payment gateway settings"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Paytm Settings",
|
||||
"description": _("Paytm payment gateway settings"),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ def delete_contact_and_address(doctype, docname):
|
|||
doc.delete()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not txt: txt = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ def get_company_address(company):
|
|||
return ret
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def address_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
|
|
|
|||
|
|
@ -183,11 +183,12 @@ def update_contact(doc, method):
|
|||
contact.save(ignore_permissions=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def contact_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
if not frappe.get_meta("Contact").get_field(searchfield)\
|
||||
or searchfield not in frappe.db.DEFAULT_COLUMNS:
|
||||
and searchfield not in frappe.db.DEFAULT_COLUMNS:
|
||||
return []
|
||||
|
||||
link_doctype = filters.pop('link_doctype')
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ frappe.ui.form.on('Data Import', {
|
|||
},
|
||||
|
||||
show_import_warnings(frm, preview_data) {
|
||||
let columns = preview_data.columns;
|
||||
let warnings = JSON.parse(frm.doc.template_warnings || '[]');
|
||||
warnings = warnings.concat(preview_data.warnings || []);
|
||||
|
||||
|
|
@ -367,11 +368,13 @@ frappe.ui.form.on('Data Import', {
|
|||
.map(warning => {
|
||||
let header = '';
|
||||
if (warning.col) {
|
||||
header = __('Column {0}', [warning.col]);
|
||||
let column_number = `<span class="text-uppercase">${__('Column {0}', [warning.col])}</span>`;
|
||||
let column_header = columns[warning.col].header_title;
|
||||
header = `${column_number} (${column_header})`;
|
||||
}
|
||||
return `
|
||||
<div class="warning" data-col="${warning.col}">
|
||||
<h5 class="text-uppercase">${header}</h5>
|
||||
<h5>${header}</h5>
|
||||
<div class="body">${warning.message}</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ frappe.listview_settings['Data Import'] = {
|
|||
get_indicator: function(doc) {
|
||||
var colors = {
|
||||
'Pending': 'orange',
|
||||
'Not Started': 'orange',
|
||||
'Partial Success': 'orange',
|
||||
'Success': 'green',
|
||||
'In Progress': 'orange',
|
||||
|
|
@ -26,6 +27,9 @@ frappe.listview_settings['Data Import'] = {
|
|||
if (imports_in_progress.includes(doc.name)) {
|
||||
status = 'In Progress';
|
||||
}
|
||||
if (status == 'Pending') {
|
||||
status = 'Not Started';
|
||||
}
|
||||
return [__(status), colors[status], 'status,=,' + doc.status];
|
||||
},
|
||||
formatters: {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import io
|
|||
import frappe
|
||||
import timeit
|
||||
import json
|
||||
from datetime import datetime
|
||||
from datetime import datetime, date
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, update_progress_bar, cstr
|
||||
from frappe.utils.csvutils import read_csv_content, get_csv_content_from_google_sheets
|
||||
|
|
@ -233,7 +233,7 @@ class Importer:
|
|||
return updated_doc
|
||||
else:
|
||||
# throw if no changes
|
||||
frappe.throw('No changes to update')
|
||||
frappe.throw("No changes to update")
|
||||
|
||||
def get_eta(self, current, total, processing_time):
|
||||
self.last_eta = getattr(self, "last_eta", 0)
|
||||
|
|
@ -322,7 +322,7 @@ class ImportFile:
|
|||
if isinstance(file, frappe.string_types):
|
||||
if frappe.db.exists("File", {"file_url": file}):
|
||||
self.file_doc = frappe.get_doc("File", {"file_url": file})
|
||||
elif 'docs.google.com/spreadsheets' in file:
|
||||
elif "docs.google.com/spreadsheets" in file:
|
||||
self.google_sheets_url = file
|
||||
elif os.path.exists(file):
|
||||
self.file_path = file
|
||||
|
|
@ -348,7 +348,7 @@ class ImportFile:
|
|||
|
||||
elif self.google_sheets_url:
|
||||
content = get_csv_content_from_google_sheets(self.google_sheets_url)
|
||||
extension = 'csv'
|
||||
extension = "csv"
|
||||
|
||||
if not content:
|
||||
frappe.throw(_("Invalid or corrupted content for import"))
|
||||
|
|
@ -465,6 +465,8 @@ class ImportFile:
|
|||
|
||||
if doctype != self.doctype and table_df:
|
||||
child_doc = row.parse_doc(doctype, parent_doc, table_df)
|
||||
if child_doc is None:
|
||||
continue
|
||||
parent_doc[table_df.fieldname] = parent_doc.get(table_df.fieldname, [])
|
||||
parent_doc[table_df.fieldname].append(child_doc)
|
||||
|
||||
|
|
@ -570,6 +572,11 @@ class Row:
|
|||
def parse_doc(self, doctype, parent_doc=None, table_df=None):
|
||||
col_indexes = self.header.get_column_indexes(doctype, table_df)
|
||||
values = self.get_values(col_indexes)
|
||||
|
||||
if all(v in INVALID_VALUES for v in values):
|
||||
# if all values are invalid, no need to parse it
|
||||
return None
|
||||
|
||||
columns = self.header.get_columns(col_indexes)
|
||||
doc = self._parse_doc(doctype, columns, values, parent_doc, table_df)
|
||||
return doc
|
||||
|
|
@ -602,12 +609,20 @@ class Row:
|
|||
|
||||
is_table = frappe.get_meta(doctype).istable
|
||||
is_update = self.import_type == UPDATE
|
||||
if is_table and is_update and doc.get("name") in INVALID_VALUES:
|
||||
# for table rows being inserted in update
|
||||
# create a new doc with defaults set
|
||||
new_doc = frappe.new_doc(doctype, as_dict=True)
|
||||
new_doc.update(doc)
|
||||
doc = new_doc
|
||||
if is_table and is_update:
|
||||
# check if the row already exists
|
||||
# if yes, fetch the original doc so that it is not updated
|
||||
# if no, create a new doc
|
||||
id_field = get_id_field(doctype)
|
||||
id_value = doc.get(id_field.fieldname)
|
||||
if id_value and frappe.db.exists(doctype, id_value):
|
||||
doc = frappe.get_doc(doctype, id_value)
|
||||
else:
|
||||
# for table rows being inserted in update
|
||||
# create a new doc with defaults set
|
||||
new_doc = frappe.new_doc(doctype, as_dict=True)
|
||||
new_doc.update(doc)
|
||||
doc = new_doc
|
||||
|
||||
self.check_mandatory_fields(doctype, doc, table_df)
|
||||
return doc
|
||||
|
|
@ -615,16 +630,12 @@ class Row:
|
|||
def validate_value(self, value, col):
|
||||
df = col.df
|
||||
if df.fieldtype == "Select":
|
||||
select_options = [d for d in (df.options or '').split('\n') if d]
|
||||
select_options = get_select_options(df)
|
||||
if select_options and value not in select_options:
|
||||
options_string = ", ".join([frappe.bold(d) for d in select_options])
|
||||
msg = _("Value must be one of {0}").format(options_string)
|
||||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"field": df_as_json(df),
|
||||
"message": msg,
|
||||
}
|
||||
{"row": self.row_number, "field": df_as_json(df), "message": msg,}
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -635,11 +646,7 @@ class Row:
|
|||
frappe.bold(value), frappe.bold(df.options)
|
||||
)
|
||||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"field": df_as_json(df),
|
||||
"message": msg,
|
||||
}
|
||||
{"row": self.row_number, "field": df_as_json(df), "message": msg,}
|
||||
)
|
||||
return
|
||||
elif df.fieldtype in ["Date", "Datetime"]:
|
||||
|
|
@ -668,7 +675,7 @@ class Row:
|
|||
|
||||
def parse_value(self, value, col):
|
||||
df = col.df
|
||||
if isinstance(value, datetime) and df.fieldtype in ["Date", "Datetime"]:
|
||||
if isinstance(value, (datetime, date)) and df.fieldtype in ["Date", "Datetime"]:
|
||||
return value
|
||||
|
||||
value = cstr(value)
|
||||
|
|
@ -689,7 +696,7 @@ class Row:
|
|||
return value
|
||||
|
||||
def get_date(self, value, column):
|
||||
if isinstance(value, datetime):
|
||||
if isinstance(value, (datetime, date)):
|
||||
return value
|
||||
|
||||
date_format = column.date_format
|
||||
|
|
@ -786,9 +793,7 @@ class Header(Row):
|
|||
for j, header in enumerate(row):
|
||||
column_values = [get_item_at_index(r, j) for r in raw_data]
|
||||
map_to_field = column_to_field_map.get(str(j))
|
||||
column = Column(
|
||||
j, header, self.doctype, column_values, map_to_field, self.seen
|
||||
)
|
||||
column = Column(j, header, self.doctype, column_values, map_to_field, self.seen)
|
||||
self.seen.append(header)
|
||||
self.columns.append(column)
|
||||
|
||||
|
|
@ -918,13 +923,20 @@ class Column:
|
|||
self.skip_import = skip_import
|
||||
|
||||
def guess_date_format_for_column(self):
|
||||
""" Guesses date format for a column by parsing all the values in the column,
|
||||
"""Guesses date format for a column by parsing all the values in the column,
|
||||
getting the date format and then returning the one which has the maximum frequency
|
||||
"""
|
||||
|
||||
date_formats = [
|
||||
frappe.utils.guess_date_format(d) for d in self.column_values if isinstance(d, str)
|
||||
]
|
||||
def guess_date_format(d):
|
||||
if isinstance(d, (datetime, date)):
|
||||
if self.df.fieldtype == "Date":
|
||||
return "%Y-%m-%d"
|
||||
if self.df.fieldtype == "Datetime":
|
||||
return "%Y-%m-%d %H:%M:%S"
|
||||
if isinstance(d, str):
|
||||
return frappe.utils.guess_date_format(d)
|
||||
|
||||
date_formats = [guess_date_format(d) for d in self.column_values]
|
||||
date_formats = [d for d in date_formats if d]
|
||||
if not date_formats:
|
||||
return
|
||||
|
|
@ -955,28 +967,61 @@ class Column:
|
|||
if not self.df:
|
||||
return
|
||||
|
||||
if self.df.fieldtype == 'Link':
|
||||
if self.skip_import:
|
||||
return
|
||||
|
||||
if self.df.fieldtype == "Link":
|
||||
# find all values that dont exist
|
||||
values = list(set([cstr(v) for v in self.column_values[1:] if v]))
|
||||
exists = [d.name for d in frappe.db.get_all(self.df.options, filters={'name': ('in', values)})]
|
||||
exists = [
|
||||
d.name for d in frappe.db.get_all(self.df.options, filters={"name": ("in", values)})
|
||||
]
|
||||
not_exists = list(set(values) - set(exists))
|
||||
if not_exists:
|
||||
missing_values = ', '.join(not_exists)
|
||||
self.warnings.append({
|
||||
'col': self.column_number,
|
||||
'message': "The following values do not exist for {}: {}".format(self.df.options, missing_values),
|
||||
'type': 'warning'
|
||||
})
|
||||
missing_values = ", ".join(not_exists)
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": (
|
||||
"The following values do not exist for {}: {}".format(
|
||||
self.df.options, missing_values
|
||||
)
|
||||
),
|
||||
"type": "warning",
|
||||
}
|
||||
)
|
||||
elif self.df.fieldtype in ("Date", "Time", "Datetime"):
|
||||
# guess date format
|
||||
self.date_format = self.guess_date_format_for_column()
|
||||
if not self.date_format:
|
||||
self.date_format = '%Y-%m-%d'
|
||||
self.warnings.append({
|
||||
'col': self.column_number,
|
||||
'message': _("Date format could not determined from the values in this column. Defaulting to yyyy-mm-dd."),
|
||||
'type': 'info'
|
||||
})
|
||||
self.date_format = "%Y-%m-%d"
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": _(
|
||||
"Date format could not be determined from the values in"
|
||||
" this column. Defaulting to yyyy-mm-dd."
|
||||
),
|
||||
"type": "info",
|
||||
}
|
||||
)
|
||||
elif self.df.fieldtype == "Select":
|
||||
options = get_select_options(self.df)
|
||||
if options:
|
||||
values = list(set([cstr(v) for v in self.column_values[1:] if v]))
|
||||
invalid = list(set(values) - set(options))
|
||||
if invalid:
|
||||
valid_values = ", ".join([frappe.bold(o) for o in options])
|
||||
invalid_values = ", ".join([frappe.bold(i) for i in invalid])
|
||||
self.warnings.append(
|
||||
{
|
||||
"col": self.column_number,
|
||||
"message": (
|
||||
"The following values are invalid: {0}. Values must be"
|
||||
" one of {1}".format(invalid_values, valid_values)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def as_dict(self):
|
||||
d = frappe._dict()
|
||||
|
|
@ -987,7 +1032,7 @@ class Column:
|
|||
d.map_to_field = self.map_to_field
|
||||
d.date_format = self.date_format
|
||||
d.df = self.df
|
||||
if hasattr(self.df, 'is_child_table_field'):
|
||||
if hasattr(self.df, "is_child_table_field"):
|
||||
d.is_child_table_field = self.df.is_child_table_field
|
||||
d.child_table_df = self.df.child_table_df
|
||||
d.skip_import = self.skip_import
|
||||
|
|
@ -1067,7 +1112,7 @@ def build_fields_dict_for_column_matching(parent_doctype):
|
|||
# other fields
|
||||
fields = get_standard_fields(doctype) + frappe.get_meta(doctype).fields
|
||||
for df in fields:
|
||||
label = (df.label or '').strip()
|
||||
label = (df.label or "").strip()
|
||||
fieldtype = df.fieldtype or "Data"
|
||||
parent = df.parent or parent_doctype
|
||||
if fieldtype not in no_value_fields:
|
||||
|
|
@ -1161,12 +1206,17 @@ def get_user_format(date_format):
|
|||
.replace("%d", "dd")
|
||||
)
|
||||
|
||||
|
||||
def df_as_json(df):
|
||||
return {
|
||||
'fieldname': df.fieldname,
|
||||
'fieldtype': df.fieldtype,
|
||||
'label': df.label,
|
||||
'options': df.options,
|
||||
'parent': df.parent,
|
||||
'default': df.default
|
||||
"fieldname": df.fieldname,
|
||||
"fieldtype": df.fieldtype,
|
||||
"label": df.label,
|
||||
"options": df.options,
|
||||
"parent": df.parent,
|
||||
"default": df.default,
|
||||
}
|
||||
|
||||
|
||||
def get_select_options(df):
|
||||
return [d for d in (df.options or "").split("\n") if d]
|
||||
|
|
|
|||
|
|
@ -3,17 +3,26 @@
|
|||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
import frappe
|
||||
import json
|
||||
from frappe.desk.doctype.bulk_update.bulk_update import show_progress
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
|
||||
class DeletedDocument(Document):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def restore(name):
|
||||
def restore(name, alert=True):
|
||||
deleted = frappe.get_doc('Deleted Document', name)
|
||||
|
||||
if deleted.restored:
|
||||
frappe.throw(_("Document {0} Already Restored").format(name), exc=frappe.DocumentAlreadyRestored)
|
||||
|
||||
doc = frappe.get_doc(json.loads(deleted.data))
|
||||
|
||||
try:
|
||||
doc.insert()
|
||||
except frappe.DocstatusTransitionError:
|
||||
|
|
@ -27,4 +36,34 @@ def restore(name):
|
|||
deleted.restored = 1
|
||||
deleted.db_update()
|
||||
|
||||
frappe.msgprint(_('Document Restored'))
|
||||
if alert:
|
||||
frappe.msgprint(_('Document Restored'))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def bulk_restore(docnames):
|
||||
docnames = frappe.parse_json(docnames)
|
||||
message = _('Restoring Deleted Document')
|
||||
restored, invalid, failed = [], [], []
|
||||
|
||||
for i, d in enumerate(docnames):
|
||||
try:
|
||||
show_progress(docnames, message, i + 1, d)
|
||||
restore(d, alert=False)
|
||||
frappe.db.commit()
|
||||
restored.append(d)
|
||||
|
||||
except frappe.DocumentAlreadyRestored:
|
||||
frappe.message_log.pop()
|
||||
invalid.append(d)
|
||||
|
||||
except Exception:
|
||||
frappe.message_log.pop()
|
||||
failed.append(d)
|
||||
frappe.db.rollback()
|
||||
|
||||
return {
|
||||
"restored": restored,
|
||||
"invalid": invalid,
|
||||
"failed": failed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
frappe.listview_settings["Deleted Document"] = {
|
||||
onload: function (doclist) {
|
||||
const action = () => {
|
||||
const selected_docs = doclist.get_checked_items();
|
||||
if (selected_docs.length > 0) {
|
||||
let docnames = selected_docs.map(doc => doc.name);
|
||||
frappe.call({
|
||||
method: "frappe.core.doctype.deleted_document.deleted_document.bulk_restore",
|
||||
args: { docnames },
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
function body(docnames) {
|
||||
const html = docnames.map(docname => {
|
||||
return `<li><a href='/desk#Form/Deleted Document/${docname}'>${docname}</a></li>`;
|
||||
});
|
||||
return "<br><ul>" + html.join("");
|
||||
}
|
||||
function 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);
|
||||
const invalid_summary = message(__("Documents that were already restored"), invalid);
|
||||
const failed_summary = message(__("Documents that failed to restore"), failed);
|
||||
const summary = restored_summary + invalid_summary + failed_summary;
|
||||
|
||||
frappe.msgprint(summary, __("Document Restoration Summary"), true);
|
||||
|
||||
if (restored.length > 0) {
|
||||
doclist.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
doclist.page.add_actions_menu_item(__("Restore"), action, false);
|
||||
},
|
||||
};
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
"web_view",
|
||||
"has_web_view",
|
||||
"allow_guest_to_view",
|
||||
"index_web_pages_for_search",
|
||||
"route",
|
||||
"is_published_field",
|
||||
"advanced",
|
||||
|
|
@ -472,6 +473,8 @@
|
|||
"label": "Documentation Link"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "actions",
|
||||
"fieldname": "actions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Actions"
|
||||
|
|
@ -483,6 +486,8 @@
|
|||
"options": "DocType Action"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "links",
|
||||
"fieldname": "links_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Links Section"
|
||||
|
|
@ -517,12 +522,94 @@
|
|||
"fieldname": "email_settings_sb",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Settings"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "index_web_pages_for_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "Index Web Pages for Search"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bolt",
|
||||
"idx": 6,
|
||||
"links": [],
|
||||
"modified": "2020-03-27 14:51:44.581128",
|
||||
"links": [
|
||||
{
|
||||
"group": "Views",
|
||||
"link_doctype": "Report",
|
||||
"link_fieldname": "ref_doctype"
|
||||
},
|
||||
{
|
||||
"group": "Workflow",
|
||||
"link_doctype": "Workflow",
|
||||
"link_fieldname": "document_type"
|
||||
},
|
||||
{
|
||||
"group": "Workflow",
|
||||
"link_doctype": "Notification",
|
||||
"link_fieldname": "document_type"
|
||||
},
|
||||
{
|
||||
"group": "Customization",
|
||||
"link_doctype": "Custom Field",
|
||||
"link_fieldname": "dt"
|
||||
},
|
||||
{
|
||||
"group": "Customization",
|
||||
"link_doctype": "Custom Script",
|
||||
"link_fieldname": "dt"
|
||||
},
|
||||
{
|
||||
"group": "Customization",
|
||||
"link_doctype": "Server Script",
|
||||
"link_fieldname": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"group": "Workflow",
|
||||
"link_doctype": "Webhook",
|
||||
"link_fieldname": "webhook_doctype"
|
||||
},
|
||||
{
|
||||
"group": "Views",
|
||||
"link_doctype": "Print Format",
|
||||
"link_fieldname": "doc_type"
|
||||
},
|
||||
{
|
||||
"group": "Views",
|
||||
"link_doctype": "Web Form",
|
||||
"link_fieldname": "doc_type"
|
||||
},
|
||||
{
|
||||
"group": "Views",
|
||||
"link_doctype": "Calendar View",
|
||||
"link_fieldname": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"group": "Views",
|
||||
"link_doctype": "Kanban Board",
|
||||
"link_fieldname": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"group": "Workflow",
|
||||
"link_doctype": "Onboarding Step",
|
||||
"link_fieldname": "reference_document"
|
||||
},
|
||||
{
|
||||
"group": "Rules",
|
||||
"link_doctype": "Auto Repeat",
|
||||
"link_fieldname": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"group": "Rules",
|
||||
"link_doctype": "Assignment Rule",
|
||||
"link_fieldname": "document_type"
|
||||
},
|
||||
{
|
||||
"group": "Rules",
|
||||
"link_doctype": "Energy Point Rule",
|
||||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2020-08-06 12:59:32.369095",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
|
|||
|
|
@ -989,7 +989,8 @@ def clear_permissions_cache(doctype):
|
|||
`tabHas Role`,
|
||||
`tabDocPerm`
|
||||
WHERE `tabDocPerm`.`parent` = %s
|
||||
AND `tabDocPerm`.`role` = `tabHas Role`.`role`
|
||||
AND `tabDocPerm`.`role` = `tabHas Role`.`role`
|
||||
AND `tabHas Role`.`parenttype` = 'User'
|
||||
""", doctype):
|
||||
frappe.clear_cache(user=user)
|
||||
|
||||
|
|
|
|||
|
|
@ -376,3 +376,96 @@ class TestDocType(unittest.TestCase):
|
|||
link_doc.delete()
|
||||
doc.delete()
|
||||
frappe.db.commit()
|
||||
|
||||
def test_ignore_cancelation_of_linked_doctype_during_cancell(self):
|
||||
import json
|
||||
from frappe.desk.form.linked_with import get_submitted_linked_docs, cancel_all_linked_docs
|
||||
|
||||
#create linked doctype
|
||||
link_doc = self.new_doctype('Test Linked Doctype 1')
|
||||
link_doc.is_submittable = 1
|
||||
for data in link_doc.get('permissions'):
|
||||
data.submit = 1
|
||||
data.cancel = 1
|
||||
link_doc.insert()
|
||||
|
||||
#create first parent doctype
|
||||
test_doc_1 = self.new_doctype('Test Doctype 1')
|
||||
test_doc_1.is_submittable = 1
|
||||
|
||||
field_2 = test_doc_1.append('fields', {})
|
||||
field_2.label = 'Test Linked Doctype 1'
|
||||
field_2.fieldname = 'test_linked_doctype_a'
|
||||
field_2.fieldtype = 'Link'
|
||||
field_2.options = 'Test Linked Doctype 1'
|
||||
|
||||
for data in test_doc_1.get('permissions'):
|
||||
data.submit = 1
|
||||
data.cancel = 1
|
||||
test_doc_1.insert()
|
||||
|
||||
#crete second parent doctype
|
||||
doc = self.new_doctype('Test Doctype 2')
|
||||
doc.is_submittable = 1
|
||||
|
||||
field_2 = doc.append('fields', {})
|
||||
field_2.label = 'Test Linked Doctype 1'
|
||||
field_2.fieldname = 'test_linked_doctype_a'
|
||||
field_2.fieldtype = 'Link'
|
||||
field_2.options = 'Test Linked Doctype 1'
|
||||
|
||||
for data in link_doc.get('permissions'):
|
||||
data.submit = 1
|
||||
data.cancel = 1
|
||||
doc.insert()
|
||||
|
||||
# create doctype data
|
||||
data_link_doc_1 = frappe.new_doc('Test Linked Doctype 1')
|
||||
data_link_doc_1.some_fieldname = 'Data1'
|
||||
data_link_doc_1.insert()
|
||||
data_link_doc_1.save()
|
||||
data_link_doc_1.submit()
|
||||
|
||||
data_doc_2 = frappe.new_doc('Test Doctype 1')
|
||||
data_doc_2.some_fieldname = 'Data1'
|
||||
data_doc_2.test_linked_doctype_a = data_link_doc_1.name
|
||||
data_doc_2.insert()
|
||||
data_doc_2.save()
|
||||
data_doc_2.submit()
|
||||
|
||||
data_doc = frappe.new_doc('Test Doctype 2')
|
||||
data_doc.some_fieldname = 'Data1'
|
||||
data_doc.test_linked_doctype_a = data_link_doc_1.name
|
||||
data_doc.insert()
|
||||
data_doc.save()
|
||||
data_doc.submit()
|
||||
|
||||
docs = get_submitted_linked_docs(link_doc.name, data_link_doc_1.name)
|
||||
dump_docs = json.dumps(docs.get('docs'))
|
||||
|
||||
cancel_all_linked_docs(dump_docs, ignore_doctypes_on_cancel_all=["Test Doctype 2"])
|
||||
|
||||
# checking that doc for Test Doctype 2 is not canceled
|
||||
self.assertRaises(frappe.LinkExistsError, data_link_doc_1.cancel)
|
||||
|
||||
data_doc.load_from_db()
|
||||
data_doc_2.load_from_db()
|
||||
self.assertEqual(data_link_doc_1.docstatus, 2)
|
||||
|
||||
#linked doc is canceled
|
||||
self.assertEqual(data_doc_2.docstatus, 2)
|
||||
|
||||
#ignored doctype 2 during cancel
|
||||
self.assertEqual(data_doc.docstatus, 1)
|
||||
|
||||
# delete doctype record
|
||||
data_doc.cancel()
|
||||
data_doc.delete()
|
||||
data_doc_2.delete()
|
||||
data_link_doc_1.delete()
|
||||
|
||||
# delete doctype
|
||||
link_doc.delete()
|
||||
doc.delete()
|
||||
test_doc_1.delete()
|
||||
frappe.db.commit()
|
||||
|
|
|
|||
|
|
@ -922,3 +922,40 @@ def update_existing_file_docs(doc):
|
|||
content_hash=doc.content_hash,
|
||||
file_name=doc.name
|
||||
))
|
||||
|
||||
def attach_files_to_document(doc, event):
|
||||
""" Runs on on_update hook of all documents.
|
||||
Goes through every Attach and Attach Image field and attaches
|
||||
the file url to the document if it is not already attached.
|
||||
"""
|
||||
|
||||
attach_fields = doc.meta.get(
|
||||
"fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]}
|
||||
)
|
||||
|
||||
for df in attach_fields:
|
||||
# this method runs in on_update hook of all documents
|
||||
# we dont want the update to fail if file cannot be attached for some reason
|
||||
try:
|
||||
value = doc.get(df.fieldname)
|
||||
if not value.startswith(("/files", "/private/files")):
|
||||
return
|
||||
|
||||
if frappe.db.exists("File", {
|
||||
"file_url": value,
|
||||
"attached_to_name": doc.name,
|
||||
"attached_to_doctype": doc.doctype,
|
||||
"attached_to_field": df.fieldname,
|
||||
}):
|
||||
return
|
||||
|
||||
frappe.get_doc(
|
||||
doctype="File",
|
||||
file_url=value,
|
||||
attached_to_name=doc.name,
|
||||
attached_to_doctype=doc.doctype,
|
||||
attached_to_field=df.fieldname,
|
||||
folder="Home/Attachments",
|
||||
).insert()
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error Attaching File"))
|
||||
|
|
|
|||
|
|
@ -328,3 +328,49 @@ class TestFile(unittest.TestCase):
|
|||
self.assertTrue(os.path.exists(file2.get_full_path()))
|
||||
|
||||
|
||||
class TestAttachment(unittest.TestCase):
|
||||
test_doctype = 'Test For Attachment'
|
||||
|
||||
def setUp(self):
|
||||
if frappe.db.exists('DocType', self.test_doctype):
|
||||
return
|
||||
|
||||
frappe.get_doc(
|
||||
doctype='DocType',
|
||||
name=self.test_doctype,
|
||||
module='Custom',
|
||||
custom=1,
|
||||
fields=[
|
||||
{'label': 'Title', 'fieldname': 'title', 'fieldtype': 'Data'},
|
||||
{'label': 'Attachment', 'fieldname': 'attachment', 'fieldtype': 'Attach'},
|
||||
]
|
||||
).insert()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc('DocType', self.test_doctype)
|
||||
|
||||
def test_file_attachment_on_update(self):
|
||||
doc = frappe.get_doc(
|
||||
doctype=self.test_doctype,
|
||||
title='test for attachment on update'
|
||||
).insert()
|
||||
|
||||
file = frappe.get_doc({
|
||||
'doctype': 'File',
|
||||
'file_name': 'test_attach.txt',
|
||||
'content': 'Test Content'
|
||||
})
|
||||
file.save()
|
||||
|
||||
doc.attachment = file.file_url
|
||||
doc.save()
|
||||
|
||||
exists = frappe.db.exists('File', {
|
||||
'file_name': 'test_attach.txt',
|
||||
'file_url': file.file_url,
|
||||
'attached_to_doctype': self.test_doctype,
|
||||
'attached_to_name': doc.name,
|
||||
'attached_to_field': 'attachment'
|
||||
})
|
||||
|
||||
self.assertTrue(exists)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
frappe.ui.form.on('Module Def', {
|
||||
refresh: function(frm) {
|
||||
|
||||
frappe.xcall('frappe.core.doctype.module_def.module_def.get_installed_apps').then(r => {
|
||||
frm.set_df_property('app_name', 'options', JSON.parse(r));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,172 +1,88 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:module_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:03",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:module_name",
|
||||
"creation": "2013-01-10 16:34:03",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"module_name",
|
||||
"custom",
|
||||
"app_name",
|
||||
"restrict_to_domain"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "module_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Module Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "module_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "module_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Module Name",
|
||||
"oldfieldname": "module_name",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "app_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "App Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "app_name",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "App Name",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "restrict_to_domain",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Restrict To Domain",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Domain",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "restrict_to_domain",
|
||||
"fieldtype": "Link",
|
||||
"label": "Restrict To Domain",
|
||||
"options": "Domain"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Custom"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-13 03:05:28.213656",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Module Def",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "DocType",
|
||||
"link_fieldname": "module"
|
||||
},
|
||||
{
|
||||
"link_doctype": "Desk Page",
|
||||
"link_fieldname": "module"
|
||||
}
|
||||
],
|
||||
"modified": "2020-08-06 12:39:30.740379",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Module Def",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, os
|
||||
import frappe, os, json
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ class ModuleDef(Document):
|
|||
"""If in `developer_mode`, create folder for module and
|
||||
add in `modules.txt` of app if missing."""
|
||||
frappe.clear_cache()
|
||||
if frappe.conf.get("developer_mode"):
|
||||
if not self.custom and frappe.conf.get("developer_mode"):
|
||||
self.create_modules_folder()
|
||||
self.add_to_modules_txt()
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ class ModuleDef(Document):
|
|||
def on_trash(self):
|
||||
"""Delete module name from modules.txt"""
|
||||
|
||||
if frappe.flags.in_uninstall:
|
||||
if frappe.flags.in_uninstall or self.custom:
|
||||
return
|
||||
|
||||
modules = None
|
||||
|
|
@ -60,3 +60,7 @@ class ModuleDef(Document):
|
|||
|
||||
frappe.clear_cache()
|
||||
frappe.setup_module_map()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_installed_apps():
|
||||
return json.dumps(frappe.get_installed_apps())
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Video', {
|
||||
frappe.ui.form.on('Navbar Item', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
87
frappe/core/doctype/navbar_item/navbar_item.json
Normal file
87
frappe/core/doctype/navbar_item/navbar_item.json
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-08-01 23:38:41.783206",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_label",
|
||||
"item_type",
|
||||
"route",
|
||||
"action",
|
||||
"hidden",
|
||||
"is_standard"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "item_label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Label",
|
||||
"mandatory_depends_on": "eval:doc.item_type == 'Route' || doc.item_type == 'Action'",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "item_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Type",
|
||||
"options": "Route\nAction\nSeparator",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Hidden",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"depends_on": "eval:doc.item_type == 'Route'",
|
||||
"fieldname": "route",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Route",
|
||||
"mandatory_depends_on": "eval:doc.item_type == 'Route'",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.item_type == 'Action'",
|
||||
"fieldname": "action",
|
||||
"fieldtype": "Data",
|
||||
"label": "Action",
|
||||
"mandatory_depends_on": "eval:doc.item_type == 'Action'",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-06 16:32:49.597060",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Navbar Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class Video(Document):
|
||||
class NavbarItem(Document):
|
||||
pass
|
||||
|
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
|||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestVideo(unittest.TestCase):
|
||||
class TestNavbarItem(unittest.TestCase):
|
||||
pass
|
||||
0
frappe/core/doctype/navbar_settings/__init__.py
Normal file
0
frappe/core/doctype/navbar_settings/__init__.py
Normal file
8
frappe/core/doctype/navbar_settings/navbar_settings.js
Normal file
8
frappe/core/doctype/navbar_settings/navbar_settings.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Navbar Settings', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
91
frappe/core/doctype/navbar_settings/navbar_settings.json
Normal file
91
frappe/core/doctype/navbar_settings/navbar_settings.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-08-01 23:41:12.577160",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"logo_section",
|
||||
"app_logo",
|
||||
"column_break_3",
|
||||
"logo_width",
|
||||
"section_break_2",
|
||||
"settings_dropdown",
|
||||
"help_dropdown"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "app_logo",
|
||||
"fieldtype": "Attach Image",
|
||||
"label": "Application Logo",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "settings_dropdown",
|
||||
"fieldtype": "Table",
|
||||
"label": "Settings Dropdown",
|
||||
"options": "Navbar Item",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "help_dropdown",
|
||||
"fieldtype": "Table",
|
||||
"label": "Help Dropdown",
|
||||
"options": "Navbar Item",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dropdowns",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "logo_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Application Logo",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "logo_width",
|
||||
"fieldtype": "Int",
|
||||
"label": "Logo Width",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-06 18:11:29.955835",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Navbar Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
40
frappe/core/doctype/navbar_settings/navbar_settings.py
Normal file
40
frappe/core/doctype/navbar_settings/navbar_settings.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
|
||||
class NavbarSettings(Document):
|
||||
def validate(self):
|
||||
self.validate_standard_navbar_items()
|
||||
|
||||
def validate_standard_navbar_items(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
|
||||
before_save_items = [item for item in \
|
||||
doc_before_save.help_dropdown + doc_before_save.settings_dropdown if item.is_standard]
|
||||
|
||||
after_save_items = [item for item in \
|
||||
self.help_dropdown + self.settings_dropdown if item.is_standard]
|
||||
|
||||
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()
|
||||
def get_app_logo():
|
||||
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo')
|
||||
if not app_logo:
|
||||
app_logo = frappe.get_hooks('app_logo_url')[-1]
|
||||
|
||||
return app_logo
|
||||
|
||||
def get_navbar_settings():
|
||||
navbar_settings = frappe.get_single('Navbar Settings')
|
||||
return navbar_settings
|
||||
|
||||
|
||||
|
||||
|
||||
10
frappe/core/doctype/navbar_settings/test_navbar_settings.py
Normal file
10
frappe/core/doctype/navbar_settings/test_navbar_settings.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestNavbarSettings(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -69,6 +69,40 @@ def run_background(prepared_report):
|
|||
user=frappe.session.user
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_reports_in_queued_state(report_name, filters):
|
||||
reports = frappe.get_all('Prepared Report',
|
||||
filters = {
|
||||
'report_name': report_name,
|
||||
'filters': json.dumps(json.loads(filters)),
|
||||
'status': 'Queued'
|
||||
})
|
||||
return reports
|
||||
|
||||
def delete_expired_prepared_reports():
|
||||
system_settings = frappe.get_single('System Settings')
|
||||
enable_auto_deletion = system_settings.enable_prepared_report_auto_deletion
|
||||
if enable_auto_deletion:
|
||||
expiry_period = system_settings.prepared_report_expiry_period
|
||||
prepared_reports_to_delete = frappe.get_all('Prepared Report',
|
||||
filters = {
|
||||
'creation': ['<', frappe.utils.add_days(frappe.utils.now(), -expiry_period)]
|
||||
})
|
||||
|
||||
args = {
|
||||
'reports': prepared_reports_to_delete,
|
||||
'limit': 50
|
||||
}
|
||||
|
||||
enqueue(method=delete_prepared_reports, job_name="delete_prepared_reports", **args)
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_prepared_reports(reports, limit=None):
|
||||
reports = frappe.parse_json(reports)
|
||||
for index, doc in enumerate(reports):
|
||||
if limit and index == limit:
|
||||
return
|
||||
frappe.delete_doc('Prepared Report', doc['name'], ignore_permissions=True)
|
||||
|
||||
def create_json_gz_file(data, dt, dn):
|
||||
# Storing data in CSV file causes information loss
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
frappe.ui.form.on('Report', {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.is_standard && !frappe.boot.developer_mode) {
|
||||
if (frm.doc.is_standard === "Yes" && !frappe.boot.developer_mode) {
|
||||
// make the document read-only
|
||||
frm.set_read_only();
|
||||
}
|
||||
|
|
@ -42,26 +42,6 @@ frappe.ui.form.on('Report', {
|
|||
}
|
||||
},
|
||||
|
||||
report_type: function(frm) {
|
||||
frm.set_intro("");
|
||||
switch(frm.doc.report_type) {
|
||||
case "Report Builder":
|
||||
frm.set_intro(__("Report Builder reports are managed directly by the report builder. Nothing to do."));
|
||||
break;
|
||||
case "Query Report":
|
||||
frm.set_intro(__("Write a SELECT query. Note result is not paged (all data is sent in one go).")
|
||||
+ __("To format columns, give column labels in the query.") + "<br>"
|
||||
+ __("[Label]:[Field Type]/[Options]:[Width]") + "<br><br>"
|
||||
+ __("Example:") + "<br>"
|
||||
+ "Employee:Link/Employee:200" + "<br>"
|
||||
+ "Rate:Currency:120" + "<br>")
|
||||
break;
|
||||
case "Script Report":
|
||||
frm.set_intro(__("Write a Python file in the same folder where this is saved and return column and result."));
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
set_doctype_roles: function(frm) {
|
||||
return frm.call('set_doctype_roles').then(() => {
|
||||
frm.refresh_field('roles');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "field:report_name",
|
||||
"creation": "2013-03-09 15:45:57",
|
||||
"doctype": "DocType",
|
||||
|
|
@ -17,10 +18,15 @@
|
|||
"disabled",
|
||||
"disable_prepared_report",
|
||||
"prepared_report",
|
||||
"filters_section",
|
||||
"filters",
|
||||
"columns_section",
|
||||
"columns",
|
||||
"section_break_6",
|
||||
"query",
|
||||
"javascript",
|
||||
"report_script",
|
||||
"client_code_section",
|
||||
"javascript",
|
||||
"json",
|
||||
"permission_rules",
|
||||
"roles"
|
||||
|
|
@ -94,7 +100,8 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Query / Script"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.report_type==\"Query Report\"",
|
||||
|
|
@ -142,15 +149,50 @@
|
|||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.report_type==\"Script Report\" && doc.is_standard===\"No\"",
|
||||
"description": "output in the form of `data = [columns, result]`",
|
||||
"depends_on": "eval:(doc.report_type===\"Script Report\" \n|| doc.report_type==\"Query Report\") \n&& doc.is_standard===\"No\"",
|
||||
"description": "Filters will be accessible via <code>filters</code>. <br><br>Send output as <code>result = [result]</code>, or for old style <code>data = [columns], [result]</code>",
|
||||
"fieldname": "report_script",
|
||||
"fieldtype": "Code",
|
||||
"label": "Script"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "filters",
|
||||
"fieldname": "filters_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Filters"
|
||||
},
|
||||
{
|
||||
"fieldname": "filters",
|
||||
"fieldtype": "Table",
|
||||
"label": "Filters",
|
||||
"options": "Report Filter"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "columns",
|
||||
"fieldname": "columns_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Table",
|
||||
"label": "Columns",
|
||||
"options": "Report Column"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "javascript",
|
||||
"fieldname": "client_code_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Client Code"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"modified": "2019-10-09 15:43:08.577610",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-17 16:49:28.474274",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ class Report(Document):
|
|||
def on_trash(self):
|
||||
delete_custom_role('report', self.name)
|
||||
|
||||
def get_columns(self):
|
||||
return [d.as_dict(no_default_fields = True) for d in self.columns]
|
||||
|
||||
def set_doctype_roles(self):
|
||||
if not self.get('roles') and self.is_standard == 'No':
|
||||
meta = frappe.get_meta(self.ref_doctype)
|
||||
|
|
@ -99,8 +102,8 @@ class Report(Document):
|
|||
if not self.query.lower().startswith("select"):
|
||||
frappe.throw(_("Query must be a SELECT"), title=_('Report Document Error'))
|
||||
|
||||
result = [list(t) for t in frappe.db.sql(self.query, filters)]
|
||||
columns = [cstr(c[0]) for c in frappe.db.get_description()]
|
||||
result = [list(t) for t in frappe.db.sql(self.query, filters, debug=True)]
|
||||
columns = self.get_columns() or [cstr(c[0]) for c in frappe.db.get_description()]
|
||||
|
||||
return [columns, result]
|
||||
|
||||
|
|
@ -134,121 +137,167 @@ class Report(Document):
|
|||
|
||||
def execute_script(self, filters):
|
||||
# server script
|
||||
loc = {"filters": frappe._dict(filters), 'data':[]}
|
||||
loc = {"filters": frappe._dict(filters), 'data':None, 'result':None}
|
||||
safe_exec(self.report_script, None, loc)
|
||||
return loc['data']
|
||||
if loc['data']:
|
||||
return loc['data']
|
||||
else:
|
||||
return self.get_columns(), loc['result']
|
||||
|
||||
def get_data(self, filters=None, limit=None, user=None, as_dict=False, ignore_prepared_report=False):
|
||||
columns = []
|
||||
out = []
|
||||
|
||||
if self.report_type in ('Query Report', 'Script Report', 'Custom Report'):
|
||||
# query and script reports
|
||||
data = frappe.desk.query_report.run(self.name,
|
||||
filters=filters, user=user, ignore_prepared_report=ignore_prepared_report)
|
||||
|
||||
for d in data.get('columns'):
|
||||
if isinstance(d, dict):
|
||||
col = frappe._dict(d)
|
||||
if not col.fieldname:
|
||||
col.fieldname = col.label
|
||||
columns.append(col)
|
||||
else:
|
||||
fieldtype, options = "Data", None
|
||||
parts = d.split(':')
|
||||
if len(parts) > 1:
|
||||
if parts[1]:
|
||||
fieldtype, options = parts[1], None
|
||||
if fieldtype and '/' in fieldtype:
|
||||
fieldtype, options = fieldtype.split('/')
|
||||
|
||||
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0], options=options))
|
||||
|
||||
out += data.get('result')
|
||||
columns, result = self.run_query_report(filters, user, ignore_prepared_report)
|
||||
else:
|
||||
# standard report
|
||||
params = json.loads(self.json)
|
||||
|
||||
if params.get('fields'):
|
||||
columns = params.get('fields')
|
||||
elif params.get('columns'):
|
||||
columns = params.get('columns')
|
||||
elif params.get('fields'):
|
||||
columns = params.get('fields')
|
||||
else:
|
||||
columns = [['name', self.ref_doctype]]
|
||||
for df in frappe.get_meta(self.ref_doctype).fields:
|
||||
if df.in_list_view:
|
||||
columns.append([df.fieldname, self.ref_doctype])
|
||||
|
||||
_filters = params.get('filters') or []
|
||||
|
||||
if filters:
|
||||
for key, value in iteritems(filters):
|
||||
condition, _value = '=', value
|
||||
if isinstance(value, (list, tuple)):
|
||||
condition, _value = value
|
||||
_filters.append([key, condition, _value])
|
||||
|
||||
def _format(parts):
|
||||
# sort by is saved as DocType.fieldname, covert it to sql
|
||||
return '`tab{0}`.`{1}`'.format(*parts)
|
||||
|
||||
if params.get('sort_by'):
|
||||
order_by = _format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order')
|
||||
elif params.get('order_by'):
|
||||
order_by = params.get('order_by')
|
||||
else:
|
||||
order_by = _format([self.ref_doctype, 'modified']) + ' desc'
|
||||
|
||||
if params.get('sort_by_next'):
|
||||
order_by += ', ' + _format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next')
|
||||
|
||||
result = frappe.get_list(self.ref_doctype,
|
||||
fields = [_format([c[1], c[0]]) for c in columns],
|
||||
filters=_filters,
|
||||
order_by = order_by,
|
||||
as_list=True,
|
||||
limit=limit,
|
||||
user=user)
|
||||
|
||||
_columns = []
|
||||
|
||||
for (fieldname, doctype) in columns:
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
if meta.get_field(fieldname):
|
||||
field = meta.get_field(fieldname)
|
||||
else:
|
||||
field = frappe._dict(fieldname=fieldname, label=meta.get_label(fieldname))
|
||||
# since name is the primary key for a document, it will always be a Link datatype
|
||||
if fieldname == "name":
|
||||
field.fieldtype = "Link"
|
||||
field.options = doctype
|
||||
|
||||
_columns.append(field)
|
||||
columns = _columns
|
||||
|
||||
out = out + [list(d) for d in result]
|
||||
|
||||
if params.get('add_totals_row'):
|
||||
out = append_totals_row(out)
|
||||
columns, result = self.run_standard_report(filters, limit, user)
|
||||
|
||||
if as_dict:
|
||||
data = []
|
||||
for row in out:
|
||||
if isinstance(row, (list, tuple)):
|
||||
_row = frappe._dict()
|
||||
for i, val in enumerate(row):
|
||||
_row[columns[i].get('fieldname')] = val
|
||||
elif isinstance(row, dict):
|
||||
# no need to convert from dict to dict
|
||||
_row = frappe._dict(row)
|
||||
data.append(_row)
|
||||
else:
|
||||
data = out
|
||||
return columns, data
|
||||
result = self.build_data_dict(result, columns)
|
||||
|
||||
return columns, result
|
||||
|
||||
def run_query_report(self, filters, user, ignore_prepared_report=False):
|
||||
columns, result = [], []
|
||||
data = frappe.desk.query_report.run(self.name,
|
||||
filters=filters, user=user, ignore_prepared_report=ignore_prepared_report)
|
||||
|
||||
for d in data.get('columns'):
|
||||
if isinstance(d, dict):
|
||||
col = frappe._dict(d)
|
||||
if not col.fieldname:
|
||||
col.fieldname = col.label
|
||||
columns.append(col)
|
||||
else:
|
||||
fieldtype, options = "Data", None
|
||||
parts = d.split(':')
|
||||
if len(parts) > 1:
|
||||
if parts[1]:
|
||||
fieldtype, options = parts[1], None
|
||||
if fieldtype and '/' in fieldtype:
|
||||
fieldtype, options = fieldtype.split('/')
|
||||
|
||||
columns.append(frappe._dict(label=parts[0], fieldtype=fieldtype, fieldname=parts[0], options=options))
|
||||
|
||||
result += data.get('result')
|
||||
|
||||
return columns, result
|
||||
|
||||
def run_standard_report(self, filters, limit, user):
|
||||
params = json.loads(self.json)
|
||||
columns = self.get_standard_report_columns(params)
|
||||
result = []
|
||||
order_by, group_by, group_by_args = self.get_standard_report_order_by(params)
|
||||
|
||||
_result = frappe.get_list(self.ref_doctype,
|
||||
fields = [
|
||||
get_group_by_field(group_by_args, c[1]) if c[0] == '_aggregate_column' and group_by_args
|
||||
else Report._format([c[1], c[0]]) for c in columns
|
||||
],
|
||||
filters = self.get_standard_report_filters(params, filters),
|
||||
order_by = order_by,
|
||||
group_by = group_by,
|
||||
as_list = True,
|
||||
limit = limit,
|
||||
user = user)
|
||||
|
||||
columns = self.build_standard_report_columns(columns, group_by_args)
|
||||
|
||||
result = result + [list(d) for d in _result]
|
||||
|
||||
if params.get('add_totals_row'):
|
||||
result = append_totals_row(result)
|
||||
|
||||
return columns, result
|
||||
|
||||
@staticmethod
|
||||
def _format(parts):
|
||||
# sort by is saved as DocType.fieldname, covert it to sql
|
||||
return '`tab{0}`.`{1}`'.format(*parts)
|
||||
|
||||
def get_standard_report_columns(self, params):
|
||||
if params.get('fields'):
|
||||
columns = params.get('fields')
|
||||
elif params.get('columns'):
|
||||
columns = params.get('columns')
|
||||
elif params.get('fields'):
|
||||
columns = params.get('fields')
|
||||
else:
|
||||
columns = [['name', self.ref_doctype]]
|
||||
for df in frappe.get_meta(self.ref_doctype).fields:
|
||||
if df.in_list_view:
|
||||
columns.append([df.fieldname, self.ref_doctype])
|
||||
|
||||
return columns
|
||||
|
||||
def get_standard_report_filters(self, params, filters):
|
||||
_filters = params.get('filters') or []
|
||||
|
||||
if filters:
|
||||
for key, value in iteritems(filters):
|
||||
condition, _value = '=', value
|
||||
if isinstance(value, (list, tuple)):
|
||||
condition, _value = value
|
||||
_filters.append([key, condition, _value])
|
||||
|
||||
return _filters
|
||||
|
||||
def get_standard_report_order_by(self, params):
|
||||
group_by_args = None
|
||||
if params.get('sort_by'):
|
||||
order_by = Report._format(params.get('sort_by').split('.')) + ' ' + params.get('sort_order')
|
||||
|
||||
elif params.get('order_by'):
|
||||
order_by = params.get('order_by')
|
||||
else:
|
||||
order_by = Report._format([self.ref_doctype, 'modified']) + ' desc'
|
||||
|
||||
if params.get('sort_by_next'):
|
||||
order_by += ', ' + Report._format(params.get('sort_by_next').split('.')) + ' ' + params.get('sort_order_next')
|
||||
|
||||
group_by = None
|
||||
if params.get('group_by'):
|
||||
group_by_args = frappe._dict(params['group_by'])
|
||||
group_by = group_by_args['group_by']
|
||||
order_by = '_aggregate_column desc'
|
||||
|
||||
return order_by, group_by, group_by_args
|
||||
|
||||
def build_standard_report_columns(self, columns, group_by_args):
|
||||
_columns = []
|
||||
|
||||
for (fieldname, doctype) in columns:
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
if meta.get_field(fieldname):
|
||||
field = meta.get_field(fieldname)
|
||||
else:
|
||||
if fieldname == '_aggregate_column':
|
||||
label = get_group_by_column_label(group_by_args, meta)
|
||||
else:
|
||||
label = meta.get_label(fieldname)
|
||||
|
||||
field = frappe._dict(fieldname=fieldname, label=label)
|
||||
|
||||
# since name is the primary key for a document, it will always be a Link datatype
|
||||
if fieldname == "name":
|
||||
field.fieldtype = "Link"
|
||||
field.options = doctype
|
||||
|
||||
_columns.append(field)
|
||||
return _columns
|
||||
|
||||
def build_data_dict(self, result, columns):
|
||||
data = []
|
||||
for row in result:
|
||||
if isinstance(row, (list, tuple)):
|
||||
_row = frappe._dict()
|
||||
for i, val in enumerate(row):
|
||||
_row[columns[i].get('fieldname')] = val
|
||||
elif isinstance(row, dict):
|
||||
# no need to convert from dict to dict
|
||||
_row = frappe._dict(row)
|
||||
data.append(_row)
|
||||
|
||||
return data
|
||||
|
||||
@Document.whitelist
|
||||
def toggle_disable(self, disable):
|
||||
|
|
@ -262,3 +311,30 @@ def is_prepared_report_disabled(report):
|
|||
def get_report_module_dotted_path(module, report_name):
|
||||
return frappe.local.module_app[scrub(module)] + "." + scrub(module) \
|
||||
+ ".report." + scrub(report_name) + "." + scrub(report_name)
|
||||
|
||||
def get_group_by_field(args, doctype):
|
||||
if args['aggregate_function'] == 'count':
|
||||
group_by_field = 'count(*) as _aggregate_column'
|
||||
else:
|
||||
group_by_field = '{0}(`tab{1}`.{2}) as _aggregate_column'.format(
|
||||
args.aggregate_function,
|
||||
doctype,
|
||||
args.aggregate_on
|
||||
)
|
||||
|
||||
return group_by_field
|
||||
|
||||
def get_group_by_column_label(args, meta):
|
||||
if args['aggregate_function'] == 'count':
|
||||
label = 'Count'
|
||||
else:
|
||||
sql_fn_map = {
|
||||
'avg': 'Average',
|
||||
'sum': 'Sum'
|
||||
}
|
||||
aggregate_on_label = meta.get_label(args.aggregate_on)
|
||||
label = _('{function} of {fieldlabel}').format(
|
||||
function=sql_fn_map[args.aggregate_function],
|
||||
fieldlabel = aggregate_on_label
|
||||
)
|
||||
return label
|
||||
|
|
|
|||
|
|
@ -111,3 +111,41 @@ data = [
|
|||
# check values
|
||||
self.assertTrue('System User' in [d.get('type') for d in data[1]])
|
||||
|
||||
def test_script_report_with_columns(self):
|
||||
report_name = 'Test Script Report With Columns'
|
||||
|
||||
if frappe.db.exists("Report", report_name):
|
||||
frappe.delete_doc('Report', report_name)
|
||||
|
||||
report = frappe.get_doc({
|
||||
'doctype': 'Report',
|
||||
'ref_doctype': 'User',
|
||||
'report_name': report_name,
|
||||
'report_type': 'Script Report',
|
||||
'is_standard': 'No',
|
||||
'columns': [
|
||||
dict(fieldname='type', label='Type', fieldtype='Data'),
|
||||
dict(fieldname='value', label='Value', fieldtype='Int'),
|
||||
]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
report.report_script = '''
|
||||
totals = {}
|
||||
for user in frappe.get_all('User', fields = ['name', 'user_type', 'creation']):
|
||||
if not user.user_type in totals:
|
||||
totals[user.user_type] = 0
|
||||
totals[user.user_type] = totals[user.user_type] + 1
|
||||
|
||||
result = [
|
||||
{"type":key, "value": value} for key, value in totals.items()
|
||||
]
|
||||
'''
|
||||
|
||||
report.save()
|
||||
data = report.get_data()
|
||||
|
||||
# check columns
|
||||
self.assertEqual(data[0][0]['label'], 'Type')
|
||||
|
||||
# check values
|
||||
self.assertTrue('System User' in [d.get('type') for d in data[1]])
|
||||
|
|
|
|||
0
frappe/core/doctype/report_column/__init__.py
Normal file
0
frappe/core/doctype/report_column/__init__.py
Normal file
61
frappe/core/doctype/report_column/report_column.json
Normal file
61
frappe/core/doctype/report_column/report_column.json
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-01-14 11:28:37.583656",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"fieldname",
|
||||
"label",
|
||||
"fieldtype",
|
||||
"options",
|
||||
"width"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"options": "Check\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Data",
|
||||
"label": "Options"
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Int",
|
||||
"label": "Width"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-17 14:32:17.174796",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report Column",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/report_column/report_column.py
Normal file
10
frappe/core/doctype/report_column/report_column.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ReportColumn(Document):
|
||||
pass
|
||||
0
frappe/core/doctype/report_filter/__init__.py
Normal file
0
frappe/core/doctype/report_filter/__init__.py
Normal file
71
frappe/core/doctype/report_filter/report_filter.json
Normal file
71
frappe/core/doctype/report_filter/report_filter.json
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-01-14 11:38:58.016498",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"fieldname",
|
||||
"label",
|
||||
"fieldtype",
|
||||
"mandatory",
|
||||
"options",
|
||||
"wildcard_filter"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"options": "Check\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "mandatory",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory"
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Data",
|
||||
"label": "Options"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Will add \"%\" before and after the query",
|
||||
"fieldname": "wildcard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Wildcard Filter"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-17 16:15:46.937267",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report Filter",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/core/doctype/report_filter/report_filter.py
Normal file
10
frappe/core/doctype/report_filter/report_filter.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ReportFilter(Document):
|
||||
pass
|
||||
|
|
@ -1,216 +1,90 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:role_name",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-08 15:50:01",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 0,
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:role_name",
|
||||
"creation": "2013-01-08 15:50:01",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"role_name",
|
||||
"home_page",
|
||||
"restrict_to_domain",
|
||||
"column_break_4",
|
||||
"disabled",
|
||||
"desk_access",
|
||||
"two_factor_auth"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "role_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Role Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "role_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "role_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Role Name",
|
||||
"oldfieldname": "role_name",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "If disabled, this role will be removed from all users.",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"description": "If disabled, this role will be removed from all users.",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "desk_access",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Desk Access",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "1",
|
||||
"fieldname": "desk_access",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Desk Access"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "two_factor_auth",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Two Factor Authentication",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "two_factor_auth",
|
||||
"fieldtype": "Check",
|
||||
"label": "Two Factor Authentication"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "restrict_to_domain",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Restrict To Domain",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Domain",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "restrict_to_domain",
|
||||
"fieldtype": "Link",
|
||||
"label": "Restrict To Domain",
|
||||
"options": "Domain"
|
||||
},
|
||||
{
|
||||
"description": "Route: Example \"/desk\"",
|
||||
"fieldname": "home_page",
|
||||
"fieldtype": "Data",
|
||||
"label": "Home Page"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-bookmark",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-06 12:42:57.097914",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Role",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-bookmark",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-06 15:42:59.036960",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Role",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -33,16 +33,22 @@ class Role(Document):
|
|||
if user_type != user.user_type:
|
||||
user.save()
|
||||
|
||||
# Get email addresses of all users that have been assigned this role
|
||||
def get_emails_from_role(role):
|
||||
emails = []
|
||||
|
||||
for user in get_users(role):
|
||||
user_email, enabled = frappe.db.get_value("User", user, ["email", "enabled"])
|
||||
if enabled and user_email not in ["admin@example.com", "guest@example.com"]:
|
||||
emails.append(user_email)
|
||||
def get_info_based_on_role(role, field='email'):
|
||||
''' Get information of all users that have been assigned this role '''
|
||||
users = frappe.get_list("Has Role", filters={"role": role, "parenttype": "User"},
|
||||
fields=["parent"])
|
||||
|
||||
return emails
|
||||
return get_user_info(users, field)
|
||||
|
||||
def get_user_info(users, field='email'):
|
||||
''' Fetch details about users for the specified field '''
|
||||
info_list = []
|
||||
for user in users:
|
||||
user_info, enabled = frappe.db.get_value("User", user.parent, [field, "enabled"])
|
||||
if enabled and user_info not in ["admin@example.com", "guest@example.com"]:
|
||||
info_list.append(user_info)
|
||||
return info_list
|
||||
|
||||
def get_users(role):
|
||||
return [d.parent for d in frappe.get_all("Has Role", filters={"role": role, "parenttype": "User"},
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Server Script', {
|
||||
setup: function(frm) {
|
||||
frm.trigger('setup_help');
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.script_type === 'Scheduler Event' && !frm.doc.disabled){
|
||||
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",
|
||||
|
|
@ -33,14 +36,50 @@ frappe.ui.form.on('Server Script', {
|
|||
}
|
||||
},
|
||||
|
||||
schedule_script(frm, data){
|
||||
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(`
|
||||
<h3>Examples</h3>
|
||||
<h4>DocType Event</h4>
|
||||
<pre><code>
|
||||
# set property
|
||||
if "test" in doc.description:
|
||||
doc.status = 'Closed'
|
||||
|
||||
|
||||
# validate
|
||||
if "validate" in doc.description:
|
||||
raise frappe.ValidationError
|
||||
|
||||
# auto create another document
|
||||
if doc.allocted_to:
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'ToDo'
|
||||
owner = doc.allocated_to,
|
||||
description = doc.subject
|
||||
)).insert()
|
||||
</code></pre>
|
||||
<hr>
|
||||
|
||||
<h4>API Call</h4>
|
||||
<pre><code>
|
||||
# respond to API
|
||||
|
||||
if frappe.form_dict.message == "ping":
|
||||
frappe.response['message'] = "pong"
|
||||
else:
|
||||
frappe.response['message'] = "ok"
|
||||
</code></pre>
|
||||
`);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
"api_method",
|
||||
"allow_guest",
|
||||
"section_break_8",
|
||||
"script"
|
||||
"script",
|
||||
"help_section",
|
||||
"help_html"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -72,10 +74,19 @@
|
|||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "help_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Help"
|
||||
},
|
||||
{
|
||||
"fieldname": "help_html",
|
||||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-06 11:24:38.161555",
|
||||
"modified": "2020-08-07 13:13:02.483963",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -52,9 +52,10 @@ class TestServerScript(unittest.TestCase):
|
|||
|
||||
frappe.db.commit()
|
||||
|
||||
# @classmethod
|
||||
# def tearDownClass(cls):
|
||||
# frappe.db.sql('truncate `tabServer Script`')
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.commit()
|
||||
frappe.db.sql('truncate `tabServer Script`')
|
||||
|
||||
def setUp(self):
|
||||
frappe.cache().delete_value('server_script_map')
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ class SMSSettings(Document):
|
|||
def validate_receiver_nos(receiver_list):
|
||||
validated_receiver_list = []
|
||||
for d in receiver_list:
|
||||
if not d:
|
||||
break
|
||||
|
||||
# remove invalid character
|
||||
for x in [' ','-', '(', ')']:
|
||||
d = d.replace(x, '')
|
||||
|
|
|
|||
|
|
@ -27,3 +27,11 @@ frappe.ui.form.on("System Settings", "enable_two_factor_auth", function(frm) {
|
|||
frm.set_value("bypass_restrict_ip_check_if_2fa_enabled", 0);
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("System Settings", "enable_prepared_report_auto_deletion", function(frm) {
|
||||
if (frm.doc.enable_prepared_report_auto_deletion) {
|
||||
if (!frm.doc.prepared_report_expiry_period) {
|
||||
frm.set_value('prepared_report_expiry_period', 7);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@
|
|||
"disable_standard_email_footer",
|
||||
"hide_footer_in_auto_email_reports",
|
||||
"attach_view_link",
|
||||
"prepared_report_section",
|
||||
"enable_prepared_report_auto_deletion",
|
||||
"prepared_report_expiry_period",
|
||||
"chat",
|
||||
"enable_chat",
|
||||
"use_socketio_to_upload_file"
|
||||
|
|
@ -429,12 +432,32 @@
|
|||
"fieldname": "attach_view_link",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send document Web View link in email"
|
||||
},
|
||||
{
|
||||
"default": "30",
|
||||
"depends_on": "enable_prepared_report_auto_deletion",
|
||||
"description": "System will automatically delete Prepared Reports after these many days since creation",
|
||||
"fieldname": "prepared_report_expiry_period",
|
||||
"fieldtype": "Int",
|
||||
"label": "Prepared Report Expiry Period (Days)"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "enable_prepared_report_auto_deletion",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Auto-deletion of Prepared Reports"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "prepared_report_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Prepared Report"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-02 16:13:00.166382",
|
||||
"modified": "2020-08-12 14:35:45.214327",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "System Settings",
|
||||
|
|
|
|||
|
|
@ -588,9 +588,70 @@
|
|||
"icon": "fa fa-user",
|
||||
"idx": 413,
|
||||
"image_field": "user_image",
|
||||
"links": [],
|
||||
"links": [
|
||||
{
|
||||
"group": "Profile",
|
||||
"link_doctype": "Contact",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Profile",
|
||||
"link_doctype": "Chat Profile",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Profile",
|
||||
"link_doctype": "Blogger",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Logs",
|
||||
"link_doctype": "Access Log",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Logs",
|
||||
"link_doctype": "Activity Log",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Logs",
|
||||
"link_doctype": "Energy Point Log",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Logs",
|
||||
"link_doctype": "Route History",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Settings",
|
||||
"link_doctype": "User Permission",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Settings",
|
||||
"link_doctype": "Assignment Rule",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Settings",
|
||||
"link_doctype": "Document Follow",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Activity",
|
||||
"link_doctype": "Communication",
|
||||
"link_fieldname": "user"
|
||||
},
|
||||
{
|
||||
"group": "Activity",
|
||||
"link_doctype": "ToDo",
|
||||
"link_fieldname": "owner"
|
||||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-04-08 12:27:36.716490",
|
||||
"modified": "2020-08-06 19:48:49.677800",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
@ -606,13 +667,13 @@
|
|||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"permlevel": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"write": 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -812,6 +812,7 @@ def reset_password(user):
|
|||
return 'not found'
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def user_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from frappe.desk.reportview import get_match_cond
|
||||
|
||||
|
|
|
|||
|
|
@ -119,6 +119,8 @@ def user_permission_exists(user, allow, for_value, applicable_for=None):
|
|||
|
||||
return has_same_user_permission
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_applicable_for_doctype_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
linked_doctypes_map = get_linked_doctypes(doctype, True)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"creation": "2018-10-17 05:47:13.087395",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"provider",
|
||||
"url",
|
||||
"column_break_4",
|
||||
"publish_date",
|
||||
"duration",
|
||||
"section_break_7",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "provider",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Provider",
|
||||
"options": "YouTube\nVimeo",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "url",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "URL",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "publish_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Publish Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Data",
|
||||
"label": "Duration"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-22 12:09:49.057403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Video",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
.restricted-button {
|
||||
cursor: default;
|
||||
position: relative;
|
||||
right: -5px;
|
||||
}
|
||||
|
|
@ -26,13 +26,6 @@ class Dashboard {
|
|||
</div>`).appendTo(this.wrapper.find(".page-content").empty());
|
||||
this.container = this.wrapper.find(".dashboard-graph");
|
||||
this.page = wrapper.page;
|
||||
|
||||
this.page.set_title_sub(
|
||||
$(`<button class="restricted-button">
|
||||
<span class="octicon octicon-lock"></span>
|
||||
<span>${__('Restricted')}</span>
|
||||
</button>`)
|
||||
);
|
||||
}
|
||||
|
||||
show() {
|
||||
|
|
|
|||
|
|
@ -334,6 +334,7 @@ frappe.PermissionEngine = Class.extend({
|
|||
});
|
||||
|
||||
this.body.on("click", "input[type='checkbox']", function() {
|
||||
frappe.dom.freeze();
|
||||
var chk = $(this);
|
||||
var args = {
|
||||
role: chk.attr("data-role"),
|
||||
|
|
@ -348,6 +349,7 @@ frappe.PermissionEngine = Class.extend({
|
|||
method: "update",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
frappe.dom.unfreeze();
|
||||
if(r.exc) {
|
||||
// exception: reverse
|
||||
chk.prop("checked", !chk.prop("checked"));
|
||||
|
|
@ -374,8 +376,7 @@ frappe.PermissionEngine = Class.extend({
|
|||
options:me.options.roles, reqd:1,fieldname:"role"},
|
||||
{fieldtype:"Select", label:__("Permission Level"),
|
||||
options:[0,1,2,3,4,5,6,7,8,9], reqd:1, fieldname: "permlevel",
|
||||
description: __("Level 0 is for document level permissions, \
|
||||
higher levels for field level permissions.")}
|
||||
description: __("Level 0 is for document level permissions, higher levels for field level permissions.")}
|
||||
]
|
||||
});
|
||||
if(me.get_doctype()) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ def get_columns_and_fields(doctype):
|
|||
|
||||
return columns, fields
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def query_doctypes(doctype, txt, searchfield, start, page_len, filters):
|
||||
user = filters.get("user")
|
||||
user_perms = frappe.utils.user.UserPermissions(user)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ def create_plan():
|
|||
'connector_name': 'Local Connector',
|
||||
'connector_type': 'Frappe',
|
||||
# connect to same host.
|
||||
'hostname': frappe.conf.host_name,
|
||||
'hostname': frappe.conf.host_name or frappe.utils.get_site_url(frappe.local.site),
|
||||
'username': 'Administrator',
|
||||
'password': 'admin'
|
||||
'password': frappe.conf.get("admin_password") or 'admin'
|
||||
}).insert(ignore_if_duplicate=True)
|
||||
|
|
|
|||
|
|
@ -134,6 +134,8 @@ class Database(object):
|
|||
if debug:
|
||||
time_start = time()
|
||||
|
||||
self.log_query(query, values, debug, explain)
|
||||
|
||||
if values!=():
|
||||
if isinstance(values, dict):
|
||||
values = dict(values)
|
||||
|
|
@ -142,41 +144,18 @@ class Database(object):
|
|||
if not isinstance(values, (dict, tuple, list)):
|
||||
values = (values,)
|
||||
|
||||
if debug and query.strip().lower().startswith('select'):
|
||||
try:
|
||||
if explain:
|
||||
self.explain_query(query, values)
|
||||
frappe.errprint(query % values)
|
||||
except TypeError:
|
||||
frappe.errprint([query, values])
|
||||
if (frappe.conf.get("logging") or False)==2:
|
||||
frappe.log("<<<< query")
|
||||
frappe.log(query)
|
||||
frappe.log("with values:")
|
||||
frappe.log(values)
|
||||
frappe.log(">>>>")
|
||||
self._cursor.execute(query, values)
|
||||
|
||||
if frappe.flags.in_migrate:
|
||||
self.log_touched_tables(query, values)
|
||||
|
||||
else:
|
||||
if debug:
|
||||
if explain:
|
||||
self.explain_query(query)
|
||||
frappe.errprint(query)
|
||||
if (frappe.conf.get("logging") or False)==2:
|
||||
frappe.log("<<<< query")
|
||||
frappe.log(query)
|
||||
frappe.log(">>>>")
|
||||
|
||||
self._cursor.execute(query)
|
||||
|
||||
if frappe.flags.in_migrate:
|
||||
self.log_touched_tables(query)
|
||||
|
||||
if debug:
|
||||
frappe.errprint(self._cursor.mogrify(query, values))
|
||||
time_end = time()
|
||||
frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2)))
|
||||
|
||||
|
|
@ -213,6 +192,33 @@ class Database(object):
|
|||
else:
|
||||
return self._cursor.fetchall()
|
||||
|
||||
def log_query(self, query, values, debug, explain):
|
||||
# for debugging in tests
|
||||
if frappe.conf.get('allow_tests') and frappe.cache().get_value('flag_print_sql'):
|
||||
print(self.mogrify(query, values))
|
||||
|
||||
# debug
|
||||
if debug:
|
||||
if explain and query.strip().lower().startswith('select'):
|
||||
self.explain_query(query, values)
|
||||
frappe.errprint(self.mogrify(query, values))
|
||||
|
||||
# info
|
||||
if (frappe.conf.get("logging") or False)==2:
|
||||
frappe.log("<<<< query")
|
||||
frappe.log(self.mogrify(query, values))
|
||||
frappe.log(">>>>")
|
||||
|
||||
def mogrify(self, query, values):
|
||||
'''build the query string with values'''
|
||||
if not values:
|
||||
return query
|
||||
else:
|
||||
try:
|
||||
return self._cursor.mogrify(query, values)
|
||||
except: # noqa: E722
|
||||
return (query, values)
|
||||
|
||||
def explain_query(self, query, values=None):
|
||||
"""Print `EXPLAIN` in error log."""
|
||||
try:
|
||||
|
|
@ -378,7 +384,7 @@ class Database(object):
|
|||
return self.get_value(doctype, filters, "*", as_dict=as_dict, cache=cache)
|
||||
|
||||
def get_value(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False,
|
||||
debug=False, order_by=None, cache=False):
|
||||
debug=False, order_by=None, cache=False, for_update=False):
|
||||
"""Returns a document property or list of properties.
|
||||
|
||||
:param doctype: DocType name.
|
||||
|
|
@ -405,12 +411,12 @@ class Database(object):
|
|||
"""
|
||||
|
||||
ret = self.get_values(doctype, filters, fieldname, ignore, as_dict, debug,
|
||||
order_by, cache=cache)
|
||||
order_by, cache=cache, for_update=for_update)
|
||||
|
||||
return ((len(ret[0]) > 1 or as_dict) and ret[0] or ret[0][0]) if ret else None
|
||||
|
||||
def get_values(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False,
|
||||
debug=False, order_by=None, update=None, cache=False):
|
||||
debug=False, order_by=None, update=None, cache=False, for_update=False):
|
||||
"""Returns multiple document properties.
|
||||
|
||||
:param doctype: DocType name.
|
||||
|
|
@ -449,7 +455,7 @@ class Database(object):
|
|||
|
||||
if (filters is not None) and (filters!=doctype or doctype=="DocType"):
|
||||
try:
|
||||
out = self._get_values_from_table(fields, filters, doctype, as_dict, debug, order_by, update)
|
||||
out = self._get_values_from_table(fields, filters, doctype, as_dict, debug, order_by, update, for_update=for_update)
|
||||
except Exception as e:
|
||||
if ignore and (frappe.db.is_missing_column(e) or frappe.db.is_table_missing(e)):
|
||||
# table or column not found, return None
|
||||
|
|
@ -576,7 +582,7 @@ class Database(object):
|
|||
"""Alias for get_single_value"""
|
||||
return self.get_single_value(*args, **kwargs)
|
||||
|
||||
def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None):
|
||||
def _get_values_from_table(self, fields, filters, doctype, as_dict, debug, order_by=None, update=None, for_update=False):
|
||||
fl = []
|
||||
if isinstance(fields, (list, tuple)):
|
||||
for f in fields:
|
||||
|
|
@ -594,9 +600,15 @@ class Database(object):
|
|||
|
||||
order_by = ("order by " + order_by) if order_by else ""
|
||||
|
||||
r = self.sql("select {0} from `tab{1}` {2} {3} {4}"
|
||||
.format(fl, doctype, "where" if conditions else "", conditions, order_by), values,
|
||||
as_dict=as_dict, debug=debug, update=update)
|
||||
r = self.sql("select {fields} from `tab{doctype}` {where} {conditions} {order_by} {for_update}"
|
||||
.format(
|
||||
for_update = 'for update' if for_update else '',
|
||||
fields = fl,
|
||||
doctype = doctype,
|
||||
where = "where" if conditions else "",
|
||||
conditions = conditions,
|
||||
order_by = order_by),
|
||||
values, as_dict=as_dict, debug=debug, update=update)
|
||||
|
||||
return r
|
||||
|
||||
|
|
@ -616,7 +628,7 @@ class Database(object):
|
|||
return self.set_value(*args, **kwargs)
|
||||
|
||||
def set_value(self, dt, dn, field, val=None, modified=None, modified_by=None,
|
||||
update_modified=True, debug=False):
|
||||
update_modified=True, debug=False, for_update=True):
|
||||
"""Set a single value in the database, do not call the ORM triggers
|
||||
but update the modified timestamp (unless specified not to).
|
||||
|
||||
|
|
@ -630,6 +642,7 @@ class Database(object):
|
|||
:param modified_by: Set this user as `modified_by`.
|
||||
:param update_modified: default True. Set as false, if you don't want to update the timestamp.
|
||||
:param debug: Print the query in the developer / js console.
|
||||
:param for_update: Will add a row-level lock to the value that is being set so that it can be released on commit.
|
||||
"""
|
||||
if not modified:
|
||||
modified = now()
|
||||
|
|
@ -647,18 +660,17 @@ class Database(object):
|
|||
|
||||
if dn and dt!=dn:
|
||||
# with table
|
||||
conditions, values = self.build_conditions(dn)
|
||||
|
||||
values.update(to_update)
|
||||
|
||||
set_values = []
|
||||
for key in to_update:
|
||||
set_values.append('`{0}`=%({0})s'.format(key))
|
||||
|
||||
self.sql("""update `tab{0}`
|
||||
set {1} where {2}""".format(dt, ', '.join(set_values), conditions),
|
||||
values, debug=debug)
|
||||
for name in self.get_values(dt, dn, 'name', for_update=for_update):
|
||||
values = dict(name=name[0])
|
||||
values.update(to_update)
|
||||
|
||||
self.sql("""update `tab{0}`
|
||||
set {1} where name=%(name)s""".format(dt, ', '.join(set_values)),
|
||||
values, debug=debug)
|
||||
else:
|
||||
# for singles
|
||||
keys = list(to_update)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class MariaDBDatabase(Database):
|
|||
'Data': ('varchar', self.VARCHAR_LEN),
|
||||
'Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('text', ''),
|
||||
'Select': ('varchar', self.VARCHAR_LEN),
|
||||
'Rating': ('int', '1'),
|
||||
'Read Only': ('varchar', self.VARCHAR_LEN),
|
||||
|
|
@ -186,7 +186,7 @@ class MariaDBDatabase(Database):
|
|||
`doctype` VARCHAR(140) NOT NULL,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`fieldname` VARCHAR(140) NOT NULL,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`password` TEXT NOT NULL,
|
||||
`encrypted` INT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`doctype`, `name`, `fieldname`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci""")
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ CREATE TABLE `__Auth` (
|
|||
`doctype` VARCHAR(140) NOT NULL,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`fieldname` VARCHAR(140) NOT NULL,
|
||||
`password` VARCHAR(255) NOT NULL,
|
||||
`password` TEXT NOT NULL,
|
||||
`encrypted` INT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`doctype`, `name`, `fieldname`)
|
||||
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class PostgresDatabase(Database):
|
|||
'Data': ('varchar', self.VARCHAR_LEN),
|
||||
'Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Dynamic Link': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('varchar', self.VARCHAR_LEN),
|
||||
'Password': ('text', ''),
|
||||
'Select': ('varchar', self.VARCHAR_LEN),
|
||||
'Rating': ('smallint', None),
|
||||
'Read Only': ('varchar', self.VARCHAR_LEN),
|
||||
|
|
@ -179,7 +179,7 @@ class PostgresDatabase(Database):
|
|||
"doctype" VARCHAR(140) NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"fieldname" VARCHAR(140) NOT NULL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"encrypted" INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("doctype", "name", "fieldname")
|
||||
)""")
|
||||
|
|
|
|||
|
|
@ -281,7 +281,7 @@ CREATE TABLE "__Auth" (
|
|||
"doctype" VARCHAR(140) NOT NULL,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"fieldname" VARCHAR(140) NOT NULL,
|
||||
"password" VARCHAR(255) NOT NULL,
|
||||
"password" TEXT NOT NULL,
|
||||
"encrypted" int NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY ("doctype", "name", "fieldname")
|
||||
);
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ class Workspace:
|
|||
|
||||
self.allowed_pages = get_allowed_pages(cache=True)
|
||||
self.allowed_reports = get_allowed_reports(cache=True)
|
||||
|
||||
|
||||
if not minimal:
|
||||
self.onboarding_doc = self.get_onboarding_doc()
|
||||
self.onboarding = None
|
||||
|
||||
|
||||
self.table_counts = get_table_with_counts()
|
||||
self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache()
|
||||
self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache()
|
||||
|
|
@ -59,7 +59,7 @@ class Workspace:
|
|||
def is_page_allowed(self):
|
||||
cards = self.doc.cards + get_custom_reports_and_doctypes(self.doc.module) + self.extended_cards
|
||||
shortcuts = self.doc.shortcuts + self.extended_shortcuts
|
||||
|
||||
|
||||
for section in cards:
|
||||
links = loads(section.links) if isinstance(section.links, string_types) else section.links
|
||||
for item in links:
|
||||
|
|
@ -195,7 +195,7 @@ class Workspace:
|
|||
'docs_url': self.onboarding_doc.documentation_url,
|
||||
'items': self.get_onboarding_steps()
|
||||
}
|
||||
|
||||
|
||||
@handle_not_exist
|
||||
def get_cards(self):
|
||||
cards = self.doc.cards
|
||||
|
|
@ -221,6 +221,8 @@ class Workspace:
|
|||
incomplete_dependencies = [d for d in item.dependencies if not _doctype_contains_a_record(d)]
|
||||
if len(incomplete_dependencies):
|
||||
item.incomplete_dependencies = incomplete_dependencies
|
||||
else:
|
||||
item.incomplete_dependencies = ""
|
||||
|
||||
if item.onboard:
|
||||
# Mark Spotlights for initial
|
||||
|
|
@ -301,7 +303,7 @@ class Workspace:
|
|||
if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item):
|
||||
if item.type == "Report":
|
||||
report = self.allowed_reports.get(item.link_to, {})
|
||||
if report.get("report_type") in ["Query Report", "Script Report"]:
|
||||
if report.get("report_type") in ["Query Report", "Script Report", "Custom Report"]:
|
||||
new_item['is_query_report'] = 1
|
||||
else:
|
||||
new_item['ref_doctype'] = report.get('ref_doctype')
|
||||
|
|
@ -356,7 +358,7 @@ def get_desk_sidebar_items(flatten=False, cache=True):
|
|||
_cache = frappe.cache()
|
||||
if cache:
|
||||
pages = _cache.get_value("desk_sidebar_items", user=frappe.session.user)
|
||||
|
||||
|
||||
if not pages or not cache:
|
||||
# don't get domain restricted pages
|
||||
blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules()
|
||||
|
|
@ -375,7 +377,7 @@ def get_desk_sidebar_items(flatten=False, cache=True):
|
|||
order_by = "pin_to_top desc, pin_to_bottom asc, name asc"
|
||||
all_pages = frappe.get_all("Desk Page", fields=["name", "category"], filters=filters, order_by=order_by, ignore_permissions=True)
|
||||
pages = []
|
||||
|
||||
|
||||
# Filter Page based on Permission
|
||||
for page in all_pages:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ frappe.ui.form.on('Dashboard', {
|
|||
refresh: function(frm) {
|
||||
frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name));
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.disable_form();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@
|
|||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard"
|
||||
"label": "Is Standard",
|
||||
"read_only_depends_on": "eval: !frappe.boot.developer_mode"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_standard",
|
||||
|
|
@ -66,7 +67,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-10 17:48:19.468813",
|
||||
"modified": "2020-07-23 11:05:41.890459",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard",
|
||||
|
|
|
|||
|
|
@ -23,43 +23,20 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
frm.chart_filters = null;
|
||||
|
||||
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
|
||||
frm.set_df_property('chart_options_section', 'hidden', 1);
|
||||
frm.disable_form();
|
||||
}
|
||||
|
||||
frm.add_custom_button('Add Chart to Dashboard', () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'dashboard',
|
||||
options: 'Dashboard',
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
values.chart_name = frm.doc.chart_name;
|
||||
frappe.xcall(
|
||||
'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard',
|
||||
{args: values}
|
||||
).then(()=> {
|
||||
let dashboard_route_html =
|
||||
`<a href = "#dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message =
|
||||
__(`Dashboard Chart ${values.chart_name} add to Dashboard ` + dashboard_route_html);
|
||||
|
||||
frappe.msgprint(message);
|
||||
});
|
||||
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
|
||||
frm.doc.name,
|
||||
'Dashboard Chart',
|
||||
'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard'
|
||||
);
|
||||
|
||||
if (!frm.doc.chart_name) {
|
||||
frappe.msgprint(__('Please create chart first'));
|
||||
} else {
|
||||
d.show();
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -79,11 +56,6 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
if (frm.doc.report_name) {
|
||||
frm.trigger('set_chart_report_filters');
|
||||
}
|
||||
|
||||
if (!frappe.boot.developer_mode) {
|
||||
frm.set_df_property("custom_options", "hidden", 1);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
is_standard: function(frm) {
|
||||
|
|
@ -313,7 +285,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
|
||||
}
|
||||
|
||||
let fields;
|
||||
let fields = [];
|
||||
if (is_document_type) {
|
||||
fields = [
|
||||
{
|
||||
|
|
@ -338,7 +310,7 @@ frappe.ui.form.on('Dashboard Chart', {
|
|||
} else if (frm.chart_filters.length) {
|
||||
fields = frm.chart_filters.filter(f => f.fieldname);
|
||||
|
||||
fields.map( f => {
|
||||
fields.map(f => {
|
||||
if (filters[f.fieldname]) {
|
||||
let condition = '=';
|
||||
const filter_row =
|
||||
|
|
|
|||
|
|
@ -240,7 +240,8 @@
|
|||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard"
|
||||
"label": "Is Standard",
|
||||
"read_only_depends_on": "eval: !frappe.boot.developer_mode"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_standard",
|
||||
|
|
@ -270,7 +271,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-21 16:37:07.763482",
|
||||
"modified": "2020-07-23 11:10:33.509497",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Dashboard Chart",
|
||||
|
|
|
|||
|
|
@ -28,15 +28,28 @@ def get_permission_query_conditions(user):
|
|||
if "System Manager" in roles:
|
||||
return None
|
||||
|
||||
allowed_doctypes = ['"%s"' % doctype for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
allowed_reports = ['"%s"' % key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
|
||||
doctype_condition = False
|
||||
report_condition = False
|
||||
|
||||
allowed_doctypes = [frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
allowed_reports = [frappe.db.escape(key) if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
|
||||
|
||||
if allowed_doctypes:
|
||||
doctype_condition = '`tabDashboard Chart`.`document_type` in ({allowed_doctypes})'.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes))
|
||||
if allowed_reports:
|
||||
report_condition = '`tabDashboard Chart`.`report_name` in ({allowed_reports})'.format(
|
||||
allowed_reports=','.join(allowed_reports))
|
||||
|
||||
return '''
|
||||
`tabDashboard Chart`.`document_type` in ({allowed_doctypes})
|
||||
or `tabDashboard Chart`.`report_name` in ({allowed_reports})
|
||||
(`tabDashboard Chart`.`chart_type` in ('Count', 'Sum', 'Average')
|
||||
and {doctype_condition})
|
||||
or
|
||||
(`tabDashboard Chart`.`chart_type` = 'Report'
|
||||
and {report_condition})
|
||||
'''.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes),
|
||||
allowed_reports=','.join(allowed_reports)
|
||||
doctype_condition=doctype_condition,
|
||||
report_condition=report_condition
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -128,7 +141,13 @@ def add_chart_to_dashboard(args):
|
|||
|
||||
dashboard = frappe.get_doc('Dashboard', args.dashboard)
|
||||
dashboard_link = frappe.new_doc('Dashboard Chart Link')
|
||||
dashboard_link.chart = args.chart_name
|
||||
dashboard_link.chart = args.chart_name or args.name
|
||||
|
||||
if args.set_standard and dashboard.is_standard:
|
||||
chart = frappe.get_doc('Dashboard Chart', dashboard_link.chart)
|
||||
chart.is_standard = 1
|
||||
chart.module = dashboard.module
|
||||
chart.save()
|
||||
|
||||
dashboard.append('charts', dashboard_link)
|
||||
dashboard.save()
|
||||
|
|
@ -338,6 +357,8 @@ def get_year_ending(date):
|
|||
# last day of this month
|
||||
return add_to_date(date, days=-1)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_charts_for_user(doctype, txt, searchfield, start, page_len, filters):
|
||||
or_filters = {'owner': frappe.session.user, 'is_public': 1}
|
||||
return frappe.db.get_list('Dashboard Chart',
|
||||
|
|
|
|||
|
|
@ -5,14 +5,23 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils.data import validate_json_string
|
||||
from frappe.modules.export_file import export_to_files
|
||||
from frappe.model.document import Document
|
||||
|
||||
class DeskPage(Document):
|
||||
def validate(self):
|
||||
self.validate_cards_json()
|
||||
if (self.is_standard and not frappe.conf.developer_mode and not disable_saving_as_standard()):
|
||||
frappe.throw(_("You need to be in developer mode to edit this document"))
|
||||
|
||||
def validate_cards_json(self):
|
||||
for card in self.cards:
|
||||
try:
|
||||
validate_json_string(card.links)
|
||||
except frappe.ValidationError:
|
||||
frappe.throw(_("Invalid JSON in card links for {0}").format(frappe.bold(card.label)))
|
||||
|
||||
def on_update(self):
|
||||
if disable_saving_as_standard():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"field_order": [
|
||||
"type",
|
||||
"link_to",
|
||||
"doc_view",
|
||||
"column_break_4",
|
||||
"label",
|
||||
"icon",
|
||||
|
|
@ -34,6 +35,15 @@
|
|||
"options": "type",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.type == \"DocType\"",
|
||||
"description": "Which view of the associated DocType should this shortcut take you to?",
|
||||
"fieldname": "doc_view",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "DocType View",
|
||||
"options": "\nList\nReport Builder\nDashboard\nTree\nNew\nCalendar"
|
||||
},
|
||||
{
|
||||
"fieldname": "stats_filter",
|
||||
"fieldtype": "Code",
|
||||
|
|
@ -86,9 +96,10 @@
|
|||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-14 16:02:15.420993",
|
||||
"modified": "2020-08-12 14:11:55.080390",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Desk Shortcut",
|
||||
|
|
|
|||
|
|
@ -82,6 +82,27 @@ class Event(Document):
|
|||
communication.add_link(participant.reference_doctype, participant.reference_docname)
|
||||
communication.save(ignore_permissions=True)
|
||||
|
||||
def add_participant(self, doctype, docname):
|
||||
"""Add a single participant to event participants
|
||||
|
||||
Args:
|
||||
doctype (string): Reference Doctype
|
||||
docname (string): Reference Docname
|
||||
"""
|
||||
self.append("event_participants", {
|
||||
"reference_doctype": doctype,
|
||||
"reference_docname": docname,
|
||||
})
|
||||
|
||||
def add_participants(self, participants):
|
||||
"""Add participant entry
|
||||
|
||||
Args:
|
||||
participants ([Array]): Array of a dict with doctype and docname
|
||||
"""
|
||||
for participant in participants:
|
||||
self.add_participant(participant["doctype"], participant["docname"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_communication(event, reference_doctype, reference_docname):
|
||||
deleted_participant = frappe.get_doc(reference_doctype, reference_docname)
|
||||
|
|
|
|||
|
|
@ -32,40 +32,16 @@ frappe.ui.form.on('Number Card', {
|
|||
|
||||
create_add_to_dashboard_button: function(frm) {
|
||||
frm.add_custom_button('Add Card to Dashboard', () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Add to Dashboard'),
|
||||
fields: [
|
||||
{
|
||||
label: __('Select Dashboard'),
|
||||
fieldtype: 'Link',
|
||||
fieldname: 'dashboard',
|
||||
options: 'Dashboard',
|
||||
}
|
||||
],
|
||||
primary_action: (values) => {
|
||||
values.name = frm.doc.name;
|
||||
frappe.xcall(
|
||||
'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard',
|
||||
{
|
||||
args: values
|
||||
}
|
||||
).then(()=> {
|
||||
let dashboard_route_html =
|
||||
`<a href = "#dashboard/${values.dashboard}">${values.dashboard}</a>`;
|
||||
let message =
|
||||
__(`Number Card ${values.name} add to Dashboard ` + dashboard_route_html);
|
||||
|
||||
frappe.msgprint(message);
|
||||
});
|
||||
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
|
||||
frm.doc.name,
|
||||
'Number Card',
|
||||
'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard'
|
||||
);
|
||||
|
||||
if (!frm.doc.name) {
|
||||
frappe.msgprint(__('Please create Card first'));
|
||||
} else {
|
||||
d.show();
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -140,6 +116,7 @@ frappe.ui.form.on('Number Card', {
|
|||
},
|
||||
|
||||
report_name: function(frm) {
|
||||
frm.filters = [];
|
||||
frm.set_value('filters_json', '{}');
|
||||
frm.set_value('dynamic_filters_json', '{}');
|
||||
frm.set_df_property('report_field', 'options', []);
|
||||
|
|
@ -271,7 +248,7 @@ frappe.ui.form.on('Number Card', {
|
|||
set_filters && frm.set_value('filters_json', JSON.stringify(filters));
|
||||
}
|
||||
|
||||
let fields;
|
||||
let fields = [];
|
||||
if (is_document_type) {
|
||||
fields = [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@
|
|||
"default": "0",
|
||||
"fieldname": "is_standard",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Standard"
|
||||
"label": "Is Standard",
|
||||
"read_only_depends_on": "eval: !frappe.boot.developer_mode"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.is_standard",
|
||||
|
|
@ -190,7 +191,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-07-18 17:08:22.882538",
|
||||
"modified": "2020-07-23 11:11:03.391719",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Number Card",
|
||||
|
|
|
|||
|
|
@ -32,13 +32,17 @@ def get_permission_query_conditions(user=None):
|
|||
if "System Manager" in roles:
|
||||
return None
|
||||
|
||||
allowed_doctypes = ['"%s"' % doctype for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
doctype_condition = False
|
||||
|
||||
allowed_doctypes = [frappe.db.escape(doctype) for doctype in frappe.permissions.get_doctypes_with_read()]
|
||||
|
||||
if allowed_doctypes:
|
||||
doctype_condition = '`tabNumber Card`.`document_type` in ({allowed_doctypes})'.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes))
|
||||
|
||||
return '''
|
||||
`tabNumber Card`.`document_type` in ({allowed_doctypes})
|
||||
'''.format(
|
||||
allowed_doctypes=','.join(allowed_doctypes)
|
||||
)
|
||||
{doctype_condition}
|
||||
'''.format(doctype_condition=doctype_condition)
|
||||
|
||||
def has_permission(doc, ptype, user):
|
||||
roles = frappe.get_roles(user)
|
||||
|
|
@ -124,11 +128,16 @@ def create_number_card(args):
|
|||
doc.insert(ignore_permissions=True)
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cards_for_user(doctype, txt, searchfield, start, page_len, filters):
|
||||
meta = frappe.get_meta(doctype)
|
||||
searchfields = meta.get_search_fields()
|
||||
search_conditions = []
|
||||
|
||||
if not frappe.db.exists('DocType', doctype):
|
||||
return
|
||||
|
||||
if txt:
|
||||
for field in searchfields:
|
||||
search_conditions.append('`tab{doctype}`.`{field}` like %(txt)s'.format(field=field, doctype=doctype, txt=txt))
|
||||
|
|
@ -172,5 +181,11 @@ def add_card_to_dashboard(args):
|
|||
dashboard_link = frappe.new_doc('Number Card Link')
|
||||
dashboard_link.card = args.name
|
||||
|
||||
if args.set_standard and dashboard.is_standard:
|
||||
card = frappe.get_doc('Number Card', dashboard_link.card)
|
||||
card.is_standard = 1
|
||||
card.module = dashboard.module
|
||||
card.save()
|
||||
|
||||
dashboard.append('cards', dashboard_link)
|
||||
dashboard.save()
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-05-18 19:42:30.435604",
|
||||
"modified": "2020-08-06 12:55:20.377679",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Onboarding Step",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue