Merge branch 'develop' of https://github.com/frappe/frappe into rebrand-ui
This commit is contained in:
commit
ba29d8d8b4
297 changed files with 5998 additions and 4254 deletions
36
.github/frappe_linter/translation.py
vendored
36
.github/frappe_linter/translation.py
vendored
|
|
@ -7,22 +7,28 @@ start_pattern = re.compile(r"_{1,2}\([\"']{1,3}")
|
|||
|
||||
# skip first argument
|
||||
files = sys.argv[1:]
|
||||
for _file in files:
|
||||
if not _file.endswith(('.py', '.js')):
|
||||
continue
|
||||
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
|
||||
|
||||
for _file in files_to_scan:
|
||||
with open(_file, 'r') as f:
|
||||
print(f'Checking: {_file}')
|
||||
for num, line in enumerate(f, 1):
|
||||
all_matches = start_pattern.finditer(line)
|
||||
if all_matches:
|
||||
for match in all_matches:
|
||||
verify = pattern.search(line)
|
||||
if not verify:
|
||||
errors_encounter += 1
|
||||
print(f'A syntax error has been discovered at line number: {num}')
|
||||
print(f'Syntax error occurred with: {line}')
|
||||
file_lines = f.readlines()
|
||||
for line_number, line in enumerate(file_lines, 1):
|
||||
start_matches = start_pattern.search(line)
|
||||
if start_matches:
|
||||
match = pattern.search(line)
|
||||
if not match and line.endswith(',\n'):
|
||||
# concat remaining text to validate multiline pattern
|
||||
line = "".join(file_lines[line_number - 1:])
|
||||
line = line[start_matches.start() + 1:]
|
||||
match = pattern.match(line)
|
||||
|
||||
if not match:
|
||||
errors_encounter += 1
|
||||
print(f'\nTranslation syntax error at line number: {line_number + 1}\n{line.strip()[:100]}')
|
||||
|
||||
if errors_encounter > 0:
|
||||
print('You can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.')
|
||||
assert 1+1 == 3
|
||||
print('\nYou can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('Good To Go!')
|
||||
print('\nGood To Go!')
|
||||
|
|
|
|||
43
.github/workflows/publish-assets-develop.yml
vendored
Normal file
43
.github/workflows/publish-assets-develop.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: Build and Publish Assets for Development
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'frappe'
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
python-version: '12.x'
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.6'
|
||||
- name: Set up bench for current push
|
||||
run: |
|
||||
npm install -g yarn
|
||||
pip3 install -U frappe-bench
|
||||
bench init frappe-bench --no-procfile --no-backups --skip-assets --skip-redis-config-generation --python $(which python) --frappe-path $GITHUB_WORKSPACE/frappe
|
||||
cd frappe-bench && bench build
|
||||
|
||||
- name: Package assets
|
||||
run: |
|
||||
mkdir -p $GITHUB_WORKSPACE/build
|
||||
tar -cvpzf $GITHUB_WORKSPACE/build/$GITHUB_SHA.tar.gz ./frappe-bench/sites/assets/js ./frappe-bench/sites/assets/css
|
||||
|
||||
- name: Publish assets to S3
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
with:
|
||||
args: --acl public-read
|
||||
env:
|
||||
AWS_S3_BUCKET: 'assets.frappeframework.com'
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ASSETS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_ASSETS_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: 'http://s3.fr-par.scw.cloud'
|
||||
AWS_REGION: 'fr-par'
|
||||
SOURCE_DIR: '$GITHUB_WORKSPACE/build'
|
||||
47
.github/workflows/publish-assets-releases.yml
vendored
Normal file
47
.github/workflows/publish-assets-releases.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Build and Publish Assets built for Releases
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: 'frappe'
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
python-version: '12.x'
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.6'
|
||||
- name: Set up bench for current push
|
||||
run: |
|
||||
npm install -g yarn
|
||||
pip3 install -U frappe-bench
|
||||
bench init frappe-bench --no-procfile --no-backups --skip-assets --skip-redis-config-generation --python $(which python) --frappe-path $GITHUB_WORKSPACE/frappe
|
||||
cd frappe-bench && bench build
|
||||
|
||||
- name: Package assets
|
||||
run: |
|
||||
mkdir -p $GITHUB_WORKSPACE/build
|
||||
tar -cvpzf $GITHUB_WORKSPACE/build/assets.tar.gz ./frappe-bench/sites/assets/js ./frappe-bench/sites/assets/css
|
||||
|
||||
- name: Get release
|
||||
id: get_release
|
||||
uses: bruceadams/get-release@v1.2.0
|
||||
|
||||
- name: Upload built Assets to Release
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
with:
|
||||
upload_url: ${{ steps.get_release.outputs.upload_url }}
|
||||
asset_path: build/assets.tar.gz
|
||||
asset_name: assets.tar.gz
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
34
.snyk
34
.snyk
|
|
@ -65,3 +65,37 @@ patch:
|
|||
patched: '2020-04-30T23:02:32.330Z'
|
||||
- quill-image-resize > lodash:
|
||||
patched: '2020-08-24T23:06:37.710Z'
|
||||
- node-sass > lodash:
|
||||
patched: '2020-09-15T23:06:41.931Z'
|
||||
- node-sass > sass-graph > lodash:
|
||||
patched: '2020-09-15T23:06:41.931Z'
|
||||
- node-sass > gaze > globule > lodash:
|
||||
patched: '2020-09-15T23:06:41.931Z'
|
||||
- snyk > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-cpp-plugin > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-go-plugin > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-gradle-plugin > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-docker-plugin > snyk-nodejs-lockfile-parser > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-mvn-plugin > @snyk/java-call-graph-builder > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-php-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-gradle-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-mvn-plugin > @snyk/cli-interface > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > @snyk/dep-graph > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-nodejs-lockfile-parser > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
- snyk > snyk-go-plugin > graphlib > lodash:
|
||||
patched: '2020-09-16T23:06:38.881Z'
|
||||
|
|
|
|||
|
|
@ -59,15 +59,18 @@ context('Recorder', () => {
|
|||
cy.get('.title-text').should('contain', 'DocType');
|
||||
cy.get('.list-count').should('contain', '20 of ');
|
||||
|
||||
cy.visit('/desk#recorder');
|
||||
// temporarily commenting out theses tests as they seem to be
|
||||
// randomly failing maybe due a backround event
|
||||
|
||||
cy.get('.list-row-container span').contains('frappe.desk.reportview.get').click();
|
||||
// cy.visit('/desk#recorder');
|
||||
|
||||
cy.location('hash').should('contain', '#recorder/request/');
|
||||
cy.get('form').should('contain', 'frappe.desk.reportview.get');
|
||||
// cy.get('.list-row-container span').contains('/api/method/frappe').click();
|
||||
|
||||
cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
|
||||
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
|
||||
cy.location('hash').should('eq', '#recorder');
|
||||
// cy.location('hash').should('contain', '#recorder/request/');
|
||||
// cy.get('form').should('contain', '/api/method/frappe');
|
||||
|
||||
// cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
|
||||
// cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
|
||||
// cy.location('hash').should('eq', '#recorder');
|
||||
});
|
||||
});
|
||||
|
|
@ -10,7 +10,7 @@ from six import iteritems, binary_type, text_type, string_types, PY2
|
|||
from werkzeug.local import Local, release_local
|
||||
import os, sys, importlib, inspect, json
|
||||
from past.builtins import cmp
|
||||
|
||||
import click
|
||||
from faker import Faker
|
||||
|
||||
# public
|
||||
|
|
@ -182,6 +182,7 @@ def init(site, sites_path=None, new_site=False):
|
|||
local.meta_cache = {}
|
||||
local.form_dict = _dict()
|
||||
local.session = _dict()
|
||||
local.dev_server = os.environ.get('DEV_SERVER', False)
|
||||
|
||||
setup_module_map()
|
||||
|
||||
|
|
@ -225,12 +226,20 @@ def get_site_config(sites_path=None, site_path=None):
|
|||
if sites_path:
|
||||
common_site_config = os.path.join(sites_path, "common_site_config.json")
|
||||
if os.path.exists(common_site_config):
|
||||
config.update(get_file_json(common_site_config))
|
||||
try:
|
||||
config.update(get_file_json(common_site_config))
|
||||
except Exception as error:
|
||||
click.secho("common_site_config.json is invalid", fg="red")
|
||||
print(error)
|
||||
|
||||
if site_path:
|
||||
site_config = os.path.join(site_path, "site_config.json")
|
||||
if os.path.exists(site_config):
|
||||
config.update(get_file_json(site_config))
|
||||
try:
|
||||
config.update(get_file_json(site_config))
|
||||
except Exception as error:
|
||||
click.secho("{0}/site_config.json is invalid".format(local.site), fg="red")
|
||||
print(error)
|
||||
elif local.site and not local.flags.new_site:
|
||||
raise IncorrectSitePath("{0} does not exist".format(local.site))
|
||||
|
||||
|
|
@ -513,12 +522,15 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
|
|||
whitelisted = []
|
||||
guest_methods = []
|
||||
xss_safe_methods = []
|
||||
def whitelist(allow_guest=False, xss_safe=False):
|
||||
allowed_http_methods_for_whitelisted_func = {}
|
||||
|
||||
def whitelist(allow_guest=False, xss_safe=False, methods=None):
|
||||
"""
|
||||
Decorator for whitelisting a function and making it accessible via HTTP.
|
||||
Standard request will be `/api/method/[path.to.method]`
|
||||
|
||||
:param allow_guest: Allow non logged-in user to access this method.
|
||||
:param methods: Allowed http method to access the method.
|
||||
|
||||
Use as:
|
||||
|
||||
|
|
@ -526,10 +538,16 @@ def whitelist(allow_guest=False, xss_safe=False):
|
|||
def myfunc(param1, param2):
|
||||
pass
|
||||
"""
|
||||
|
||||
if not methods:
|
||||
methods = ['GET', 'POST', 'PUT', 'DELETE']
|
||||
|
||||
def innerfn(fn):
|
||||
global whitelisted, guest_methods, xss_safe_methods
|
||||
global whitelisted, guest_methods, xss_safe_methods, allowed_http_methods_for_whitelisted_func
|
||||
whitelisted.append(fn)
|
||||
|
||||
allowed_http_methods_for_whitelisted_func[fn] = methods
|
||||
|
||||
if allow_guest:
|
||||
guest_methods.append(fn)
|
||||
|
||||
|
|
@ -1109,8 +1127,8 @@ def get_newargs(fn, kwargs):
|
|||
if (a in fnargs) or varkw:
|
||||
newargs[a] = kwargs.get(a)
|
||||
|
||||
if "flags" in newargs:
|
||||
del newargs["flags"]
|
||||
newargs.pop("ignore_permissions", None)
|
||||
newargs.pop("flags", None)
|
||||
|
||||
return newargs
|
||||
|
||||
|
|
|
|||
|
|
@ -193,7 +193,8 @@ def handle_exception(e):
|
|||
|
||||
else:
|
||||
traceback = "<pre>" + sanitize_html(frappe.get_traceback()) + "</pre>"
|
||||
if frappe.local.flags.disable_traceback:
|
||||
# disable traceback in production if flag is set
|
||||
if frappe.local.flags.disable_traceback and not frappe.local.dev_server:
|
||||
traceback = ""
|
||||
|
||||
frappe.respond_as_web_page("Server Error",
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ class AutoRepeat(Document):
|
|||
|
||||
def make_new_document(self):
|
||||
reference_doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
new_doc = frappe.copy_doc(reference_doc)
|
||||
new_doc = frappe.copy_doc(reference_doc, ignore_no_copy = False)
|
||||
self.update_doc(new_doc, reference_doc)
|
||||
new_doc.insert(ignore_permissions = True)
|
||||
|
||||
|
|
@ -403,6 +403,7 @@ def update_reference(docname, reference):
|
|||
|
||||
@frappe.whitelist()
|
||||
def generate_message_preview(reference_dt, reference_doc, message=None, subject=None):
|
||||
frappe.has_permission("Auto Repeat", "write", throw=True)
|
||||
doc = frappe.get_doc(reference_dt, reference_doc)
|
||||
subject_preview = _("Please add a subject to your email")
|
||||
msg_preview = frappe.render_template(message, {'doc': doc})
|
||||
|
|
|
|||
232
frappe/build.py
232
frappe/build.py
|
|
@ -11,24 +11,141 @@ import warnings
|
|||
import tempfile
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
from six import iteritems, text_type
|
||||
|
||||
import frappe
|
||||
from frappe.utils.minify import JavascriptMinify
|
||||
|
||||
import click
|
||||
from requests import get
|
||||
from six import iteritems, text_type
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
|
||||
timestamps = {}
|
||||
app_paths = None
|
||||
sites_path = os.path.abspath(os.getcwd())
|
||||
|
||||
|
||||
def download_file(url, prefix):
|
||||
filename = urlparse(url).path.split("/")[-1]
|
||||
local_filename = os.path.join(prefix, filename)
|
||||
with get(url, stream=True, allow_redirects=True) as r:
|
||||
r.raise_for_status()
|
||||
with open(local_filename, "wb") as f:
|
||||
for chunk in r.iter_content(chunk_size=8192):
|
||||
f.write(chunk)
|
||||
return local_filename
|
||||
|
||||
|
||||
def build_missing_files():
|
||||
# check which files dont exist yet from the build.json and tell build.js to build only those!
|
||||
missing_assets = []
|
||||
current_asset_files = []
|
||||
|
||||
for type in ["css", "js"]:
|
||||
current_asset_files.extend(
|
||||
[
|
||||
"{0}/{1}".format(type, name)
|
||||
for name in os.listdir(os.path.join(sites_path, "assets", type))
|
||||
]
|
||||
)
|
||||
|
||||
with open(os.path.join(sites_path, "assets", "frappe", "build.json")) as f:
|
||||
all_asset_files = json.load(f).keys()
|
||||
|
||||
for asset in all_asset_files:
|
||||
if asset.replace("concat:", "") not in current_asset_files:
|
||||
missing_assets.append(asset)
|
||||
|
||||
if missing_assets:
|
||||
from subprocess import check_call
|
||||
from shlex import split
|
||||
|
||||
click.secho("\nBuilding missing assets...\n", fg="yellow")
|
||||
command = split(
|
||||
"node rollup/build.js --files {0} --no-concat".format(",".join(missing_assets))
|
||||
)
|
||||
check_call(command, cwd=os.path.join("..", "apps", "frappe"))
|
||||
|
||||
|
||||
def get_assets_link(frappe_head):
|
||||
from subprocess import getoutput
|
||||
from requests import head
|
||||
|
||||
tag = getoutput(
|
||||
"cd ../apps/frappe && git show-ref --tags -d | grep %s | sed -e 's,.*"
|
||||
" refs/tags/,,' -e 's/\^{}//'"
|
||||
% frappe_head
|
||||
)
|
||||
|
||||
if tag:
|
||||
# if tag exists, download assets from github release
|
||||
url = "https://github.com/frappe/frappe/releases/download/{0}/assets.tar.gz".format(tag)
|
||||
else:
|
||||
url = "http://assets.frappeframework.com/{0}.tar.gz".format(frappe_head)
|
||||
|
||||
if not head(url):
|
||||
raise ValueError("URL {0} doesn't exist".format(url))
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def download_frappe_assets(verbose=True):
|
||||
"""Downloads and sets up Frappe assets if they exist based on the current
|
||||
commit HEAD.
|
||||
Returns True if correctly setup else returns False.
|
||||
"""
|
||||
from simple_chalk import green
|
||||
from subprocess import getoutput
|
||||
from tempfile import mkdtemp
|
||||
|
||||
assets_setup = False
|
||||
frappe_head = getoutput("cd ../apps/frappe && git rev-parse HEAD")
|
||||
|
||||
if frappe_head:
|
||||
try:
|
||||
url = get_assets_link(frappe_head)
|
||||
click.secho("Retreiving assets...", fg="yellow")
|
||||
prefix = mkdtemp(prefix="frappe-assets-", suffix=frappe_head)
|
||||
assets_archive = download_file(url, prefix)
|
||||
print("\n{0} Downloaded Frappe assets from {1}".format(green('✔'), url))
|
||||
|
||||
if assets_archive:
|
||||
import tarfile
|
||||
|
||||
click.secho("\nExtracting assets...\n", fg="yellow")
|
||||
with tarfile.open(assets_archive) as tar:
|
||||
for file in tar:
|
||||
if not file.isdir():
|
||||
dest = "." + file.name.replace("./frappe-bench/sites", "")
|
||||
show = dest.replace("./assets/", "")
|
||||
tar.makefile(file, dest)
|
||||
print("{0} Restored {1}".format(green('✔'), show))
|
||||
|
||||
build_missing_files()
|
||||
return True
|
||||
else:
|
||||
raise
|
||||
except Exception:
|
||||
# TODO: log traceback in bench.log
|
||||
click.secho("An Error occurred while downloading assets...", fg="red")
|
||||
assets_setup = False
|
||||
finally:
|
||||
try:
|
||||
shutil.rmtree(os.path.dirname(assets_archive))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return assets_setup
|
||||
|
||||
|
||||
def symlink(target, link_name, overwrite=False):
|
||||
'''
|
||||
"""
|
||||
Create a symbolic link named link_name pointing to target.
|
||||
If link_name exists then FileExistsError is raised, unless overwrite=True.
|
||||
When trying to overwrite a directory, IsADirectoryError is raised.
|
||||
|
||||
Source: https://stackoverflow.com/a/55742015/10309266
|
||||
'''
|
||||
"""
|
||||
|
||||
if not overwrite:
|
||||
return os.symlink(target, link_name)
|
||||
|
|
@ -76,27 +193,28 @@ def setup():
|
|||
|
||||
|
||||
def get_node_pacman():
|
||||
pacmans = ['yarn', 'npm']
|
||||
for exec_ in pacmans:
|
||||
exec_ = find_executable(exec_)
|
||||
if exec_:
|
||||
return exec_
|
||||
raise ValueError('No Node.js Package Manager found.')
|
||||
exec_ = find_executable("yarn")
|
||||
if exec_:
|
||||
return exec_
|
||||
raise ValueError("Yarn not found")
|
||||
|
||||
|
||||
def bundle(no_compress, app=None, make_copy=False, restore=False, verbose=False):
|
||||
def bundle(no_compress, app=None, make_copy=False, restore=False, verbose=False, skip_frappe=False):
|
||||
"""concat / minify js files"""
|
||||
setup()
|
||||
make_asset_dirs(make_copy=make_copy, restore=restore)
|
||||
|
||||
pacman = get_node_pacman()
|
||||
mode = 'build' if no_compress else 'production'
|
||||
command = '{pacman} run {mode}'.format(pacman=pacman, mode=mode)
|
||||
mode = "build" if no_compress else "production"
|
||||
command = "{pacman} run {mode}".format(pacman=pacman, mode=mode)
|
||||
|
||||
if app:
|
||||
command += ' --app {app}'.format(app=app)
|
||||
command += " --app {app}".format(app=app)
|
||||
|
||||
frappe_app_path = os.path.abspath(os.path.join(app_paths[0], '..'))
|
||||
if skip_frappe:
|
||||
command += " --skip_frappe"
|
||||
|
||||
frappe_app_path = os.path.abspath(os.path.join(app_paths[0], ".."))
|
||||
check_yarn()
|
||||
frappe.commands.popen(command, cwd=frappe_app_path)
|
||||
|
||||
|
|
@ -107,22 +225,22 @@ def watch(no_compress):
|
|||
|
||||
pacman = get_node_pacman()
|
||||
|
||||
frappe_app_path = os.path.abspath(os.path.join(app_paths[0], '..'))
|
||||
frappe_app_path = os.path.abspath(os.path.join(app_paths[0], ".."))
|
||||
check_yarn()
|
||||
frappe_app_path = frappe.get_app_path('frappe', '..')
|
||||
frappe.commands.popen('{pacman} run watch'.format(pacman=pacman), cwd=frappe_app_path)
|
||||
frappe_app_path = frappe.get_app_path("frappe", "..")
|
||||
frappe.commands.popen("{pacman} run watch".format(pacman=pacman), cwd=frappe_app_path)
|
||||
|
||||
|
||||
def check_yarn():
|
||||
if not find_executable('yarn'):
|
||||
print('Please install yarn using below command and try again.\nnpm install -g yarn')
|
||||
if not find_executable("yarn"):
|
||||
print("Please install yarn using below command and try again.\nnpm install -g yarn")
|
||||
|
||||
|
||||
def make_asset_dirs(make_copy=False, restore=False):
|
||||
# don't even think of making assets_path absolute - rm -rf ahead.
|
||||
assets_path = os.path.join(frappe.local.sites_path, "assets")
|
||||
|
||||
for dir_path in [os.path.join(assets_path, 'js'), os.path.join(assets_path, 'css')]:
|
||||
for dir_path in [os.path.join(assets_path, "js"), os.path.join(assets_path, "css")]:
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
|
||||
|
|
@ -131,24 +249,27 @@ def make_asset_dirs(make_copy=False, restore=False):
|
|||
app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__))
|
||||
|
||||
symlinks = []
|
||||
app_public_path = os.path.join(app_base_path, 'public')
|
||||
app_public_path = os.path.join(app_base_path, "public")
|
||||
# app/public > assets/app
|
||||
symlinks.append([app_public_path, os.path.join(assets_path, app_name)])
|
||||
# app/node_modules > assets/app/node_modules
|
||||
if os.path.exists(os.path.abspath(app_public_path)):
|
||||
symlinks.append([os.path.join(app_base_path, '..', 'node_modules'), os.path.join(
|
||||
assets_path, app_name, 'node_modules')])
|
||||
symlinks.append(
|
||||
[
|
||||
os.path.join(app_base_path, "..", "node_modules"),
|
||||
os.path.join(assets_path, app_name, "node_modules"),
|
||||
]
|
||||
)
|
||||
|
||||
app_doc_path = None
|
||||
if os.path.isdir(os.path.join(app_base_path, 'docs')):
|
||||
app_doc_path = os.path.join(app_base_path, 'docs')
|
||||
if os.path.isdir(os.path.join(app_base_path, "docs")):
|
||||
app_doc_path = os.path.join(app_base_path, "docs")
|
||||
|
||||
elif os.path.isdir(os.path.join(app_base_path, 'www', 'docs')):
|
||||
app_doc_path = os.path.join(app_base_path, 'www', 'docs')
|
||||
elif os.path.isdir(os.path.join(app_base_path, "www", "docs")):
|
||||
app_doc_path = os.path.join(app_base_path, "www", "docs")
|
||||
|
||||
if app_doc_path:
|
||||
symlinks.append([app_doc_path, os.path.join(
|
||||
assets_path, app_name + '_docs')])
|
||||
symlinks.append([app_doc_path, os.path.join(assets_path, app_name + "_docs")])
|
||||
|
||||
for source, target in symlinks:
|
||||
source = os.path.abspath(source)
|
||||
|
|
@ -162,7 +283,7 @@ def make_asset_dirs(make_copy=False, restore=False):
|
|||
shutil.copytree(source, target)
|
||||
elif make_copy:
|
||||
if os.path.exists(target):
|
||||
warnings.warn('Target {target} already exists.'.format(target=target))
|
||||
warnings.warn("Target {target} already exists.".format(target=target))
|
||||
else:
|
||||
shutil.copytree(source, target)
|
||||
else:
|
||||
|
|
@ -174,7 +295,7 @@ def make_asset_dirs(make_copy=False, restore=False):
|
|||
try:
|
||||
symlink(source, target, overwrite=True)
|
||||
except OSError:
|
||||
print('Cannot link {} to {}'.format(source, target))
|
||||
print("Cannot link {} to {}".format(source, target))
|
||||
else:
|
||||
# warnings.warn('Source {source} does not exist.'.format(source = source))
|
||||
pass
|
||||
|
|
@ -193,7 +314,7 @@ def get_build_maps():
|
|||
|
||||
build_maps = {}
|
||||
for app_path in app_paths:
|
||||
path = os.path.join(app_path, 'public', 'build.json')
|
||||
path = os.path.join(app_path, "public", "build.json")
|
||||
if os.path.exists(path):
|
||||
with open(path) as f:
|
||||
try:
|
||||
|
|
@ -202,8 +323,7 @@ def get_build_maps():
|
|||
source_paths = []
|
||||
for source in sources:
|
||||
if isinstance(source, list):
|
||||
s = frappe.get_pymodule_path(
|
||||
source[0], *source[1].split("/"))
|
||||
s = frappe.get_pymodule_path(source[0], *source[1].split("/"))
|
||||
else:
|
||||
s = os.path.join(app_path, source)
|
||||
source_paths.append(s)
|
||||
|
|
@ -211,36 +331,42 @@ def get_build_maps():
|
|||
build_maps[target] = source_paths
|
||||
except ValueError as e:
|
||||
print(path)
|
||||
print('JSON syntax error {0}'.format(str(e)))
|
||||
print("JSON syntax error {0}".format(str(e)))
|
||||
return build_maps
|
||||
|
||||
|
||||
def pack(target, sources, no_compress, verbose):
|
||||
from six import StringIO
|
||||
|
||||
outtype, outtxt = target.split(".")[-1], ''
|
||||
outtype, outtxt = target.split(".")[-1], ""
|
||||
jsm = JavascriptMinify()
|
||||
|
||||
for f in sources:
|
||||
suffix = None
|
||||
if ':' in f:
|
||||
f, suffix = f.split(':')
|
||||
if ":" in f:
|
||||
f, suffix = f.split(":")
|
||||
if not os.path.exists(f) or os.path.isdir(f):
|
||||
print("did not find " + f)
|
||||
continue
|
||||
timestamps[f] = os.path.getmtime(f)
|
||||
try:
|
||||
with open(f, 'r') as sourcefile:
|
||||
data = text_type(sourcefile.read(), 'utf-8', errors='ignore')
|
||||
with open(f, "r") as sourcefile:
|
||||
data = text_type(sourcefile.read(), "utf-8", errors="ignore")
|
||||
|
||||
extn = f.rsplit(".", 1)[1]
|
||||
|
||||
if outtype == "js" and extn == "js" and (not no_compress) and suffix != "concat" and (".min." not in f):
|
||||
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
|
||||
if (
|
||||
outtype == "js"
|
||||
and extn == "js"
|
||||
and (not no_compress)
|
||||
and suffix != "concat"
|
||||
and (".min." not in f)
|
||||
):
|
||||
tmpin, tmpout = StringIO(data.encode("utf-8")), StringIO()
|
||||
jsm.minify(tmpin, tmpout)
|
||||
minified = tmpout.getvalue()
|
||||
if minified:
|
||||
outtxt += text_type(minified or '', 'utf-8').strip('\n') + ';'
|
||||
outtxt += text_type(minified or "", "utf-8").strip("\n") + ";"
|
||||
|
||||
if verbose:
|
||||
print("{0}: {1}k".format(f, int(len(minified) / 1024)))
|
||||
|
|
@ -248,27 +374,27 @@ def pack(target, sources, no_compress, verbose):
|
|||
# add to frappe.templates
|
||||
outtxt += html_to_js_template(f, data)
|
||||
else:
|
||||
outtxt += ('\n/*\n *\t%s\n */' % f)
|
||||
outtxt += '\n' + data + '\n'
|
||||
outtxt += "\n/*\n *\t%s\n */" % f
|
||||
outtxt += "\n" + data + "\n"
|
||||
|
||||
except Exception:
|
||||
print("--Error in:" + f + "--")
|
||||
print(frappe.get_traceback())
|
||||
|
||||
with open(target, 'w') as f:
|
||||
with open(target, "w") as f:
|
||||
f.write(outtxt.encode("utf-8"))
|
||||
|
||||
print("Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))))
|
||||
print("Wrote %s - %sk" % (target, str(int(os.path.getsize(target) / 1024))))
|
||||
|
||||
|
||||
def html_to_js_template(path, content):
|
||||
'''returns HTML template content as Javascript code, adding it to `frappe.templates`'''
|
||||
"""returns HTML template content as Javascript code, adding it to `frappe.templates`"""
|
||||
return """frappe.templates["{key}"] = '{content}';\n""".format(
|
||||
key=path.rsplit("/", 1)[-1][:-5], content=scrub_html_template(content))
|
||||
|
||||
|
||||
def scrub_html_template(content):
|
||||
'''Returns HTML content with removed whitespace and comments'''
|
||||
"""Returns HTML content with removed whitespace and comments"""
|
||||
# remove whitespace to a single space
|
||||
content = re.sub("\s+", " ", content)
|
||||
|
||||
|
|
@ -281,12 +407,12 @@ def scrub_html_template(content):
|
|||
def files_dirty():
|
||||
for target, sources in iteritems(get_build_maps()):
|
||||
for f in sources:
|
||||
if ':' in f:
|
||||
f, suffix = f.split(':')
|
||||
if ":" in f:
|
||||
f, suffix = f.split(":")
|
||||
if not os.path.exists(f) or os.path.isdir(f):
|
||||
continue
|
||||
if os.path.getmtime(f) != timestamps.get(f):
|
||||
print(f + ' dirty')
|
||||
print(f + " dirty")
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, json
|
||||
import frappe.defaults
|
||||
from frappe.model.document import Document
|
||||
from frappe.desk.notifications import (delete_notification_count_for,
|
||||
clear_notifications)
|
||||
|
|
|
|||
|
|
@ -62,11 +62,11 @@
|
|||
"label": "URLs"
|
||||
}
|
||||
],
|
||||
"modified": "2019-11-07 13:21:19.395927",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Chat",
|
||||
"name": "Chat Message",
|
||||
"owner": "arjun@gmail.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ def get_single_value(doctype, field):
|
|||
value = frappe.db.get_single_value(doctype, field)
|
||||
return value
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def set_value(doctype, name, fieldname, value=None):
|
||||
'''Set a value using get_doc, group of values
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ def set_value(doctype, name, fieldname, value=None):
|
|||
|
||||
return doc.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def insert(doc=None):
|
||||
'''Insert a document
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ def insert(doc=None):
|
|||
doc = frappe.get_doc(doc).insert()
|
||||
return doc.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def insert_many(docs=None):
|
||||
'''Insert multiple documents
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ def insert_many(docs=None):
|
|||
|
||||
return out
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def save(doc):
|
||||
'''Update (save) an existing document
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ def save(doc):
|
|||
|
||||
return doc.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def rename_doc(doctype, old_name, new_name, merge=False):
|
||||
'''Rename document
|
||||
|
||||
|
|
@ -209,7 +209,7 @@ def rename_doc(doctype, old_name, new_name, merge=False):
|
|||
new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge)
|
||||
return new_name
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def submit(doc):
|
||||
'''Submit a document
|
||||
|
||||
|
|
@ -222,7 +222,7 @@ def submit(doc):
|
|||
|
||||
return doc.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def cancel(doctype, name):
|
||||
'''Cancel a document
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ def cancel(doctype, name):
|
|||
|
||||
return wrapper.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['DELETE', 'POST'])
|
||||
def delete(doctype, name):
|
||||
'''Delete a remote document
|
||||
|
||||
|
|
@ -241,13 +241,13 @@ def delete(doctype, name):
|
|||
:param name: name of the document to be deleted'''
|
||||
frappe.delete_doc(doctype, name, ignore_missing=False)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def set_default(key, value, parent=None):
|
||||
"""set a user default value"""
|
||||
frappe.db.set_default(key, value, parent or frappe.session.user)
|
||||
frappe.clear_cache(user=frappe.session.user)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def make_width_property_setter(doc):
|
||||
'''Set width Property Setter
|
||||
|
||||
|
|
@ -257,7 +257,7 @@ def make_width_property_setter(doc):
|
|||
if doc["doctype"]=="Property Setter" and doc["property"]=="width":
|
||||
frappe.get_doc(doc).insert(ignore_permissions = True)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def bulk_update(docs):
|
||||
'''Bulk update documents
|
||||
|
||||
|
|
@ -333,7 +333,7 @@ def get_time_zone():
|
|||
'''Returns default time zone'''
|
||||
return {"time_zone": frappe.defaults.get_defaults().get("time_zone")}
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.whitelist(methods=['POST', 'PUT'])
|
||||
def attach_file(filename=None, filedata=None, doctype=None, docname=None, folder=None, decode_base64=False, is_private=None, docfield=None):
|
||||
'''Attach a file to Document (POST)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import click
|
|||
|
||||
# imports - module imports
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.commands import get_site, pass_context
|
||||
from frappe.commands.scheduler import _is_scheduler_enabled
|
||||
from frappe.exceptions import SiteNotSpecifiedError
|
||||
|
|
@ -272,11 +271,10 @@ 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, skip_search_index=False):
|
||||
def migrate(context, skip_failing=False, skip_search_index=False):
|
||||
"Run patches, sync schema and rebuild files/translations"
|
||||
from frappe.migrate import migrate
|
||||
|
||||
|
|
@ -287,7 +285,6 @@ def migrate(context, rebuild_website=False, skip_failing=False, skip_search_inde
|
|||
try:
|
||||
migrate(
|
||||
context.verbose,
|
||||
rebuild_website=rebuild_website,
|
||||
skip_failing=skip_failing,
|
||||
skip_search_index=skip_search_index
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals, absolute_import, print_function
|
||||
import click
|
||||
import json, os, sys, subprocess
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
import click
|
||||
|
||||
import frappe
|
||||
from frappe.commands import pass_context, get_site
|
||||
from frappe.commands import get_site, pass_context
|
||||
from frappe.exceptions import SiteNotSpecifiedError
|
||||
from frappe.utils import update_progress_bar, get_bench_path
|
||||
from frappe.utils.response import json_handler
|
||||
from coverage import Coverage
|
||||
import cProfile, pstats
|
||||
from six import StringIO
|
||||
from frappe.utils import get_bench_path, update_progress_bar
|
||||
|
||||
|
||||
@click.command('build')
|
||||
|
|
@ -19,14 +19,22 @@ from six import StringIO
|
|||
@click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking')
|
||||
@click.option('--restore', is_flag=True, default=False, help='Copy the files instead of symlinking with force')
|
||||
@click.option('--verbose', is_flag=True, default=False, help='Verbose')
|
||||
def build(app=None, make_copy=False, restore = False, verbose=False):
|
||||
@click.option('--force', is_flag=True, default=False, help='Force build assets instead of downloading available')
|
||||
def build(app=None, make_copy=False, restore=False, verbose=False, force=False):
|
||||
"Minify + concatenate JS and CSS files, build translations"
|
||||
import frappe.build
|
||||
import frappe
|
||||
frappe.init('')
|
||||
# don't minify in developer_mode for faster builds
|
||||
no_compress = frappe.local.conf.developer_mode or False
|
||||
frappe.build.bundle(no_compress, app=app, make_copy=make_copy, restore = restore, verbose=verbose)
|
||||
|
||||
# dont try downloading assets if force used, app specified or running via CI
|
||||
if not (force or app or os.environ.get('CI')):
|
||||
# skip building frappe if assets exist remotely
|
||||
skip_frappe = frappe.build.download_frappe_assets(verbose=verbose)
|
||||
else:
|
||||
skip_frappe = False
|
||||
|
||||
frappe.build.bundle(no_compress, app=app, make_copy=make_copy, restore=restore, verbose=verbose, skip_frappe=skip_frappe)
|
||||
|
||||
|
||||
@click.command('watch')
|
||||
|
|
@ -133,6 +141,7 @@ def reset_perms(context):
|
|||
def execute(context, method, args=None, kwargs=None, profile=False):
|
||||
"Execute a function"
|
||||
for site in context.sites:
|
||||
ret = ""
|
||||
try:
|
||||
frappe.init(site=site)
|
||||
frappe.connect()
|
||||
|
|
@ -151,12 +160,19 @@ def execute(context, method, args=None, kwargs=None, profile=False):
|
|||
kwargs = {}
|
||||
|
||||
if profile:
|
||||
import cProfile
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
|
||||
ret = frappe.get_attr(method)(*args, **kwargs)
|
||||
try:
|
||||
ret = frappe.get_attr(method)(*args, **kwargs)
|
||||
except Exception:
|
||||
ret = frappe.safe_eval(method + "(*args, **kwargs)", eval_globals=globals(), eval_locals=locals())
|
||||
|
||||
if profile:
|
||||
import pstats
|
||||
from six import StringIO
|
||||
|
||||
pr.disable()
|
||||
s = StringIO()
|
||||
pstats.Stats(pr, stream=s).sort_stats('cumulative').print_stats(.5)
|
||||
|
|
@ -167,6 +183,7 @@ def execute(context, method, args=None, kwargs=None, profile=False):
|
|||
finally:
|
||||
frappe.destroy()
|
||||
if ret:
|
||||
from frappe.utils.response import json_handler
|
||||
print(json.dumps(ret, default=json_handler))
|
||||
|
||||
if not context.sites:
|
||||
|
|
@ -288,8 +305,6 @@ def import_doc(context, path, force=False):
|
|||
@click.option('--submit-after-import', default=False, is_flag=True, help='Submit document after importing it')
|
||||
@click.option('--ignore-encoding-errors', default=False, is_flag=True, help='Ignore encoding errors while coverting to unicode')
|
||||
@click.option('--no-email', default=True, is_flag=True, help='Send email if applicable')
|
||||
|
||||
|
||||
@pass_context
|
||||
def import_csv(context, path, only_insert=False, submit_after_import=False, ignore_encoding_errors=False, no_email=True):
|
||||
"Import CSV using data import"
|
||||
|
|
@ -420,7 +435,7 @@ def jupyter(context):
|
|||
os.mkdir(jupyter_notebooks_path)
|
||||
bin_path = os.path.abspath('../env/bin')
|
||||
print('''
|
||||
Stating Jupyter notebook
|
||||
Starting Jupyter notebook
|
||||
Run the following in your first cell to connect notebook to frappe
|
||||
```
|
||||
import frappe
|
||||
|
|
@ -492,6 +507,8 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
|
|||
frappe.flags.skip_test_records = skip_test_records
|
||||
|
||||
if coverage:
|
||||
from coverage import Coverage
|
||||
|
||||
# Generate coverage report only for app that is being tested
|
||||
source_path = os.path.join(get_bench_path(), 'apps', app or 'frappe')
|
||||
cov = Coverage(source=[source_path], omit=[
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:salutation",
|
||||
"beta": 0,
|
||||
"creation": "2017-04-10 12:17:58.071915",
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-04-10 12:55:18.855578",
|
||||
"modified": "2020-09-14 12:55:18.855578",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Contacts",
|
||||
"name": "Salutation",
|
||||
|
|
@ -129,4 +129,4 @@
|
|||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,731 +1,184 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2017-10-05 11:10:38.780133",
|
||||
"custom": 0,
|
||||
"description": "Keep track of all update feeds",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"subject",
|
||||
"section_break_8",
|
||||
"content",
|
||||
"column_break_5",
|
||||
"additional_info",
|
||||
"communication_date",
|
||||
"column_break_7",
|
||||
"operation",
|
||||
"status",
|
||||
"reference_section",
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"reference_owner",
|
||||
"column_break_14",
|
||||
"timeline_doctype",
|
||||
"timeline_name",
|
||||
"link_doctype",
|
||||
"link_name",
|
||||
"user",
|
||||
"full_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Subject",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "content",
|
||||
"fieldtype": "Text Editor",
|
||||
"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": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "400"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "additional_info",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "More Information",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "More Information"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Now",
|
||||
"fieldname": "communication_date",
|
||||
"fieldtype": "Datetime",
|
||||
"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": "Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Date"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "operation",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Operation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nLogin\nLogout",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "\nLogin\nLogout"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"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": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nSuccess\nFailed\nLinked\nClosed",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "\nSuccess\nFailed\nLinked\nClosed"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reference",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_doctype",
|
||||
"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": "Reference Document Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic 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": "Reference Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "reference_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "reference_doctype"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "reference_name.owner",
|
||||
"fieldname": "reference_owner",
|
||||
"fieldtype": "Read Only",
|
||||
"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": "Reference Owner",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"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": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "timeline_doctype",
|
||||
"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": "Timeline DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "timeline_name",
|
||||
"fieldtype": "Dynamic 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": "Timeline Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "timeline_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "timeline_doctype"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_doctype",
|
||||
"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": "Link DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "link_name",
|
||||
"fieldtype": "Dynamic 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": "Link Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "link_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "__user",
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "full_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": "Full Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Full Name"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-comment",
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-09-05 14:22:27.664645",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-28 11:43:57.504565",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Activity Log",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 1,
|
||||
"if_owner": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 1,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "All",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "subject",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "subject",
|
||||
|
|
|
|||
|
|
@ -25,9 +25,6 @@ class ActivityLog(Document):
|
|||
if self.reference_doctype and self.reference_name:
|
||||
self.status = "Linked"
|
||||
|
||||
def on_trash(self): # pylint: disable=no-self-use
|
||||
frappe.throw(_("Sorry! You cannot delete auto-generated comments"))
|
||||
|
||||
def on_doctype_update():
|
||||
"""Add indexes in `tabActivity Log`"""
|
||||
frappe.db.add_index("Activity Log", ["reference_doctype", "reference_name"])
|
||||
|
|
|
|||
|
|
@ -454,18 +454,18 @@ def update_parent_document_on_communication(doc):
|
|||
# update the modified date for document
|
||||
parent.update_modified()
|
||||
|
||||
update_mins_to_first_communication(parent, doc)
|
||||
update_first_response_time(parent, doc)
|
||||
set_avg_response_time(parent, doc)
|
||||
parent.run_method("notify_communication", doc)
|
||||
parent.notify_update()
|
||||
|
||||
def update_mins_to_first_communication(parent, communication):
|
||||
if parent.meta.has_field("mins_to_first_response") and not parent.get("mins_to_first_response"):
|
||||
def update_first_response_time(parent, communication):
|
||||
if parent.meta.has_field("first_response_time") and not parent.get("first_response_time"):
|
||||
if is_system_user(communication.sender):
|
||||
first_responded_on = communication.creation
|
||||
if parent.meta.has_field("first_responded_on") and communication.sent_or_received == "Sent":
|
||||
parent.db_set("first_responded_on", first_responded_on)
|
||||
parent.db_set("mins_to_first_response", round(time_diff_in_seconds(first_responded_on, parent.creation) / 60), 2)
|
||||
parent.db_set("first_response_time", round(time_diff_in_seconds(first_responded_on, parent.creation), 2))
|
||||
|
||||
def set_avg_response_time(parent, communication):
|
||||
if parent.meta.has_field("avg_response_time") and communication.sent_or_received == "Sent":
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from frappe import _
|
|||
import frappe.permissions
|
||||
import re, csv, os
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint
|
||||
from frappe.utils import cstr, formatdate, format_datetime, parse_json, cint, format_duration
|
||||
from frappe.core.doctype.data_import_legacy.importer import get_data_keys
|
||||
from six import string_types
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
|
|
@ -330,6 +330,8 @@ class DataExporter:
|
|||
value = formatdate(value)
|
||||
elif fieldtype == "Datetime":
|
||||
value = format_datetime(value)
|
||||
elif fieldtype == "Duration":
|
||||
value = format_duration(value, df.hide_days)
|
||||
|
||||
row[_column_start_end.start + i + 1] = value
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from frappe.model import (
|
|||
no_value_fields,
|
||||
table_fields as table_fieldtypes,
|
||||
)
|
||||
from frappe.utils import flt, format_duration
|
||||
from frappe.utils.csvutils import build_csv_response
|
||||
from frappe.utils.xlsxutils import build_xlsx_response
|
||||
|
||||
|
|
@ -146,8 +147,13 @@ class Exporter:
|
|||
if df.parent == doctype:
|
||||
if df.is_child_table_field and df.child_table_df.fieldname != parentfield:
|
||||
continue
|
||||
row[i] = doc.get(df.fieldname, "")
|
||||
value = doc.get(df.fieldname, None)
|
||||
|
||||
if df.fieldtype == "Duration":
|
||||
value = flt(value or 0)
|
||||
value = format_duration(value, df.hide_days)
|
||||
|
||||
row[i] = value
|
||||
return rows
|
||||
|
||||
def get_data_as_docs(self):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Title ,Description ,Number ,another_number ,ID (Table Field 1) ,Child Title (Table Field 1) ,Child Description (Table Field 1) ,Child 2 Title (Table Field 2) ,Child 2 Date (Table Field 2) ,Child 2 Number (Table Field 2) ,Child Title (Table Field 1 Again) ,Child Date (Table Field 1 Again) ,Child Number (Table Field 1 Again) ,table_field_1_again.child_another_number
|
||||
Test ,test description ,1 ,2 ,"" ,child title ,child description ,child title ,14-08-2019 ,4 ,child title again ,22-09-2020 ,5 , 7
|
||||
, , , , ,child title 2 ,child description 2 ,title child ,30-10-2019 ,5 ,child title again 2 ,22-09-2021 , ,
|
||||
Test 2 ,test description 2 ,1 ,2 , ,child mandatory title , ,title child man , , ,child mandatory again , , ,
|
||||
Test 3 ,test description 3 ,4 ,5 ,"" ,child title asdf ,child description asdf ,child title asdf adsf ,15-08-2019 ,6 ,child title again asdf ,22-09-2022 ,9 , 71
|
||||
Title ,Description ,Number ,Duration,another_number ,ID (Table Field 1),Child Title (Table Field 1),Child Description (Table Field 1),Child 2 Title (Table Field 2),Child 2 Date (Table Field 2),Child 2 Number (Table Field 2),Child Title (Table Field 1 Again),Child Date (Table Field 1 Again),Child Number (Table Field 1 Again),table_field_1_again.child_another_number
|
||||
Test ,test description ,1,3h,2, ,child title ,child description ,child title ,14-08-2019,4,child title again ,22-09-2020,5,7
|
||||
, , ,, , ,child title 2,child description 2,title child ,30-10-2019,5,child title again 2,22-09-2021, ,
|
||||
Test 2,test description 2,1,4d 3h,2, ,child mandatory title , ,title child man , , ,child mandatory again , , ,
|
||||
Test 3,test description 3,4,5d 5h 45m,5, ,child title asdf ,child description asdf ,child title asdf adsf ,15-08-2019,6,child title again asdf ,22-09-2022,9,71
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 54.
|
|
|
@ -9,7 +9,7 @@ import timeit
|
|||
import json
|
||||
from datetime import datetime, date
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, update_progress_bar, cstr
|
||||
from frappe.utils import cint, flt, update_progress_bar, cstr, duration_to_seconds
|
||||
from frappe.utils.csvutils import read_csv_content, get_csv_content_from_google_sheets
|
||||
from frappe.utils.xlsxutils import (
|
||||
read_xlsx_file_from_attached_file,
|
||||
|
|
@ -664,6 +664,20 @@ class Row:
|
|||
}
|
||||
)
|
||||
return
|
||||
elif df.fieldtype == "Duration":
|
||||
import re
|
||||
is_valid_duration = re.match("^(?:(\d+d)?((^|\s)\d+h)?((^|\s)\d+m)?((^|\s)\d+s)?)$", value)
|
||||
if not is_valid_duration:
|
||||
self.warnings.append(
|
||||
{
|
||||
"row": self.row_number,
|
||||
"col": col.column_number,
|
||||
"field": df_as_json(df),
|
||||
"message": _("Value {0} must be in the valid duration format: d h m s").format(
|
||||
frappe.bold(value)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
|
@ -692,6 +706,8 @@ class Row:
|
|||
value = flt(value)
|
||||
elif df.fieldtype in ["Date", "Datetime"]:
|
||||
value = self.get_date(value, col)
|
||||
elif df.fieldtype == "Duration":
|
||||
value = duration_to_seconds(value)
|
||||
|
||||
return value
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import unittest
|
||||
import frappe
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import getdate, format_duration
|
||||
|
||||
doctype_name = 'DocType for Import'
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ class TestImporter(unittest.TestCase):
|
|||
|
||||
self.assertEqual(doc1.description, 'test description')
|
||||
self.assertEqual(doc1.number, 1)
|
||||
self.assertEqual(format_duration(doc1.duration), '3h')
|
||||
|
||||
self.assertEqual(doc1.table_field_1[0].child_title, 'child title')
|
||||
self.assertEqual(doc1.table_field_1[0].child_description, 'child description')
|
||||
|
|
@ -40,7 +41,10 @@ class TestImporter(unittest.TestCase):
|
|||
self.assertEqual(doc1.table_field_1_again[1].child_date, getdate('2021-09-22'))
|
||||
|
||||
self.assertEqual(doc2.description, 'test description 2')
|
||||
self.assertEqual(format_duration(doc2.duration), '4d 3h')
|
||||
|
||||
self.assertEqual(doc3.another_number, 5)
|
||||
self.assertEqual(format_duration(doc3.duration), '5d 5h 45m')
|
||||
|
||||
def test_data_import_preview(self):
|
||||
import_file = get_import_file('sample_import_file')
|
||||
|
|
@ -48,7 +52,7 @@ class TestImporter(unittest.TestCase):
|
|||
preview = data_import.get_preview_from_template()
|
||||
|
||||
self.assertEqual(len(preview.data), 4)
|
||||
self.assertEqual(len(preview.columns), 15)
|
||||
self.assertEqual(len(preview.columns), 16)
|
||||
|
||||
def test_data_import_without_mandatory_values(self):
|
||||
import_file = get_import_file('sample_import_file_without_mandatory')
|
||||
|
|
@ -146,6 +150,7 @@ def create_doctype_if_not_exists(doctype_name, force=False):
|
|||
{'label': 'Title', 'fieldname': 'title', 'reqd': 1, 'fieldtype': 'Data'},
|
||||
{'label': 'Description', 'fieldname': 'description', 'fieldtype': 'Small Text'},
|
||||
{'label': 'Date', 'fieldname': 'date', 'fieldtype': 'Date'},
|
||||
{'label': 'Duration', 'fieldname': 'duration', 'fieldtype': 'Duration'},
|
||||
{'label': 'Number', 'fieldname': 'number', 'fieldtype': 'Int'},
|
||||
{'label': 'Number', 'fieldname': 'another_number', 'fieldtype': 'Int'},
|
||||
{'label': 'Table Field 1', 'fieldname': 'table_field_1', 'fieldtype': 'Table', 'options': table_1_name},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from frappe import _
|
|||
from frappe.utils.csvutils import getlink
|
||||
from frappe.utils.dateutils import parse_date
|
||||
|
||||
from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url
|
||||
from frappe.utils import cint, cstr, flt, getdate, get_datetime, get_url, get_absolute_url, duration_to_seconds
|
||||
from six import string_types
|
||||
|
||||
|
||||
|
|
@ -164,7 +164,8 @@ def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False,
|
|||
d[fieldname] = get_datetime(_date + " " + _time)
|
||||
else:
|
||||
d[fieldname] = None
|
||||
|
||||
elif fieldtype == "Duration":
|
||||
d[fieldname] = duration_to_seconds(cstr(d[fieldname]))
|
||||
elif fieldtype in ("Image", "Attach Image", "Attach"):
|
||||
# added file to attachments list
|
||||
attachments.append(d[fieldname])
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@
|
|||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
|
|
@ -475,9 +476,10 @@
|
|||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-06 09:06:25.224413",
|
||||
"modified": "2020-08-28 11:28:21.252853",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@
|
|||
"collapsible_depends_on": "links",
|
||||
"fieldname": "links_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Links Section"
|
||||
"label": "Linked Documents"
|
||||
},
|
||||
{
|
||||
"fieldname": "links",
|
||||
|
|
@ -609,7 +609,7 @@
|
|||
"link_fieldname": "reference_doctype"
|
||||
}
|
||||
],
|
||||
"modified": "2020-08-06 12:59:32.369095",
|
||||
"modified": "2020-09-24 13:13:58.227153",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType",
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@ class DocType(Document):
|
|||
if self.default_print_format and not self.custom:
|
||||
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form'))
|
||||
|
||||
if frappe.conf.get('developer_mode'):
|
||||
self.owner = 'Administrator'
|
||||
self.modified_by = 'Administrator'
|
||||
|
||||
def set_default_in_list_view(self):
|
||||
'''Set default in-list-view for first 4 mandatory fields'''
|
||||
if not [d.fieldname for d in self.fields if d.in_list_view]:
|
||||
|
|
@ -234,6 +238,8 @@ class DocType(Document):
|
|||
|
||||
if not autoname and self.get("fields", {"fieldname":"naming_series"}):
|
||||
self.autoname = "naming_series:"
|
||||
elif self.autoname == "naming_series:" and not self.get("fields", {"fieldname":"naming_series"}):
|
||||
frappe.throw(_("Invalid fieldname '{0}' in autoname").format(self.autoname))
|
||||
|
||||
# validate field name if autoname field:fieldname is used
|
||||
# Create unique index on autoname field automatically.
|
||||
|
|
@ -634,13 +640,15 @@ class DocType(Document):
|
|||
if not name:
|
||||
name = self.name
|
||||
|
||||
flags = {"flags": re.ASCII} if six.PY3 else {}
|
||||
|
||||
# a DocType name should not start or end with an empty space
|
||||
if re.match("^[ \t\n\r]+|[ \t\n\r]+$", name, **flags):
|
||||
frappe.throw(_("DocType's name should not start or end with whitespace"), frappe.NameError)
|
||||
|
||||
# a DocType's name should not start with a number or underscore
|
||||
# and should only contain letters, numbers and underscore
|
||||
if six.PY2:
|
||||
is_a_valid_name = re.match("^(?![\W])[^\d_\s][\w ]+$", name)
|
||||
else:
|
||||
is_a_valid_name = re.match("^(?![\W])[^\d_\s][\w ]+$", name, flags = re.ASCII)
|
||||
if not is_a_valid_name:
|
||||
if not re.match("^(?![\W])[^\d_\s][\w ]+$", name, **flags):
|
||||
frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError)
|
||||
|
||||
|
||||
|
|
@ -762,7 +770,7 @@ def validate_fields(meta):
|
|||
|
||||
if not d.get("__islocal") and frappe.db.has_column(d.parent, d.fieldname):
|
||||
has_non_unique_values = frappe.db.sql("""select `{fieldname}`, count(*)
|
||||
from `tab{doctype}` where ifnull({fieldname}, '') != ''
|
||||
from `tab{doctype}` where ifnull(`{fieldname}`, '') != ''
|
||||
group by `{fieldname}` having count(*) > 1 limit 1""".format(
|
||||
doctype=d.parent, fieldname=d.fieldname))
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"label",
|
||||
"action_type",
|
||||
"action",
|
||||
"group"
|
||||
"group",
|
||||
"hidden"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -31,20 +32,28 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Action Type",
|
||||
"options": "Server Action",
|
||||
"options": "Server Action\nRoute",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 4,
|
||||
"fieldname": "action",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Action",
|
||||
"label": "Action / Route",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-09-24 09:11:39.860100",
|
||||
"links": [],
|
||||
"modified": "2020-08-21 14:44:03.845315",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocType Action",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Document Naming Rule', {
|
||||
refresh: function(frm) {
|
||||
frm.trigger('document_type');
|
||||
},
|
||||
document_type: (frm) => {
|
||||
// update the select field options with fieldnames
|
||||
if (frm.doc.document_type) {
|
||||
frappe.model.with_doctype(frm.doc.document_type, () => {
|
||||
let fieldnames = frappe.get_meta(frm.doc.document_type).fields
|
||||
.filter((d) => {
|
||||
return frappe.model.no_value_type.indexOf(d.fieldtype) === -1;
|
||||
}).map((d) => {
|
||||
return {label: `${d.label} (${d.fieldname})`, value: d.fieldname};
|
||||
});
|
||||
frappe.meta.get_docfield('Document Naming Rule Condition', 'field', frm.doc.name).options = fieldnames;
|
||||
frm.refresh_field('conditions');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-09-07 12:48:48.334318",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
"disabled",
|
||||
"priority",
|
||||
"section_break_3",
|
||||
"conditions",
|
||||
"naming_section",
|
||||
"prefix",
|
||||
"prefix_digits",
|
||||
"counter"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "document_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Document Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "prefix",
|
||||
"fieldtype": "Data",
|
||||
"label": "Prefix",
|
||||
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\""
|
||||
},
|
||||
{
|
||||
"fieldname": "counter",
|
||||
"fieldtype": "Int",
|
||||
"label": "Counter",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "5",
|
||||
"description": "Example: 00001",
|
||||
"fieldname": "prefix_digits",
|
||||
"fieldtype": "Int",
|
||||
"label": "Digits",
|
||||
"mandatory_depends_on": "eval:doc.naming_by===\"Numbered\""
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Naming"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "conditions",
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Rule Conditions"
|
||||
},
|
||||
{
|
||||
"fieldname": "conditions",
|
||||
"fieldtype": "Table",
|
||||
"label": "Conditions",
|
||||
"options": "Document Naming Rule Condition"
|
||||
},
|
||||
{
|
||||
"description": "Rules with higher priority will be applied first.",
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Int",
|
||||
"label": "Priority"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-21 10:23:34.401539",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Document Naming Rule",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "document_type",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# -*- 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.utils.data import evaluate_filters
|
||||
|
||||
class DocumentNamingRule(Document):
|
||||
def apply(self, doc):
|
||||
'''
|
||||
Apply naming rules for the given document. Will set `name` if the rule is matched.
|
||||
'''
|
||||
if self.conditions:
|
||||
if not evaluate_filters(doc, [(d.field, d.condition, d.value) for d in self.conditions]):
|
||||
return
|
||||
|
||||
counter = frappe.db.get_value(self.doctype, self.name, 'counter', for_update=True) or 0
|
||||
doc.name = self.prefix + ('%0'+str(self.prefix_digits)+'d') % (counter + 1)
|
||||
frappe.db.set_value(self.doctype, self.name, 'counter', counter + 1)
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestDocumentNamingRule(unittest.TestCase):
|
||||
def test_naming_rule_by_series(self):
|
||||
naming_rule = frappe.get_doc(dict(
|
||||
doctype = 'Document Naming Rule',
|
||||
document_type = 'ToDo',
|
||||
prefix = 'test-todo-',
|
||||
prefix_digits = 5
|
||||
)).insert()
|
||||
|
||||
todo = frappe.get_doc(dict(
|
||||
doctype = 'ToDo',
|
||||
description = 'Is this my name ' + frappe.generate_hash()
|
||||
)).insert()
|
||||
|
||||
self.assertEqual(todo.name, 'test-todo-00001')
|
||||
|
||||
naming_rule.delete()
|
||||
todo.delete()
|
||||
|
||||
def test_naming_rule_by_condition(self):
|
||||
naming_rule = frappe.get_doc(dict(
|
||||
doctype = 'Document Naming Rule',
|
||||
document_type = 'ToDo',
|
||||
prefix = 'test-high-',
|
||||
prefix_digits = 5,
|
||||
priority = 10,
|
||||
conditions = [dict(
|
||||
field = 'priority',
|
||||
condition = '=',
|
||||
value = 'High'
|
||||
)]
|
||||
)).insert()
|
||||
|
||||
# another rule
|
||||
naming_rule_1 = frappe.copy_doc(naming_rule)
|
||||
naming_rule_1.prefix = 'test-medium-'
|
||||
naming_rule_1.conditions[0].value = 'Medium'
|
||||
naming_rule_1.insert()
|
||||
|
||||
# default rule with low priority - should not get applied for rules
|
||||
# with higher priority
|
||||
naming_rule_2 = frappe.copy_doc(naming_rule)
|
||||
naming_rule_2.prefix = 'test-low-'
|
||||
naming_rule_2.priority = 0
|
||||
naming_rule_2.conditions = []
|
||||
naming_rule_2.insert()
|
||||
|
||||
|
||||
todo = frappe.get_doc(dict(
|
||||
doctype = 'ToDo',
|
||||
priority = 'High',
|
||||
description = 'Is this my name ' + frappe.generate_hash()
|
||||
)).insert()
|
||||
|
||||
todo_1 = frappe.get_doc(dict(
|
||||
doctype = 'ToDo',
|
||||
priority = 'Medium',
|
||||
description = 'Is this my name ' + frappe.generate_hash()
|
||||
)).insert()
|
||||
|
||||
todo_2 = frappe.get_doc(dict(
|
||||
doctype = 'ToDo',
|
||||
priority = 'Low',
|
||||
description = 'Is this my name ' + frappe.generate_hash()
|
||||
)).insert()
|
||||
|
||||
try:
|
||||
self.assertEqual(todo.name, 'test-high-00001')
|
||||
self.assertEqual(todo_1.name, 'test-medium-00001')
|
||||
self.assertEqual(todo_2.name, 'test-low-00001')
|
||||
finally:
|
||||
naming_rule.delete()
|
||||
naming_rule_1.delete()
|
||||
naming_rule_2.delete()
|
||||
todo.delete()
|
||||
todo_1.delete()
|
||||
todo_2.delete()
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Document Naming Rule Condition', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"actions": [],
|
||||
"creation": "2020-09-08 10:17:54.366279",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"field",
|
||||
"condition",
|
||||
"value"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Field",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Condition",
|
||||
"options": "=\n!=\n>\n<\n>=\n<=",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "value",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Value",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-08 10:19:56.192949",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Document Naming Rule Condition",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -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 DocumentNamingRuleCondition(Document):
|
||||
pass
|
||||
|
|
@ -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 TestDocumentNamingRuleCondition(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -17,11 +17,11 @@
|
|||
"unique": 1
|
||||
}
|
||||
],
|
||||
"modified": "2019-06-30 13:24:13.732202",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Domain",
|
||||
"owner": "makarand@erpnext.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
|
|
|
|||
|
|
@ -278,25 +278,26 @@ class File(Document):
|
|||
base_url = os.path.dirname(self.file_url)
|
||||
|
||||
files = []
|
||||
with zipfile.ZipFile(zip_path) as zf:
|
||||
zf.extractall(os.path.dirname(zip_path))
|
||||
for info in zf.infolist():
|
||||
if not info.filename.startswith('__MACOSX'):
|
||||
file_url = file_url = base_url + '/' + info.filename
|
||||
file_name = frappe.db.get_value('File', dict(file_url=file_url))
|
||||
if file_name:
|
||||
file_doc = frappe.get_doc('File', file_name)
|
||||
else:
|
||||
file_doc = frappe.new_doc("File")
|
||||
file_doc.file_name = info.filename
|
||||
file_doc.file_size = info.file_size
|
||||
file_doc.folder = self.folder
|
||||
file_doc.is_private = self.is_private
|
||||
file_doc.file_url = file_url
|
||||
file_doc.attached_to_doctype = self.attached_to_doctype
|
||||
file_doc.attached_to_name = self.attached_to_name
|
||||
file_doc.save()
|
||||
files.append(file_doc)
|
||||
with zipfile.ZipFile(zip_path) as z:
|
||||
for file in z.filelist:
|
||||
if file.is_dir() or file.filename.startswith('__MACOSX/'):
|
||||
# skip directories and macos hidden directory
|
||||
continue
|
||||
|
||||
filename = os.path.basename(file.filename)
|
||||
if filename.startswith('.'):
|
||||
# skip hidden files
|
||||
continue
|
||||
|
||||
file_doc = frappe.new_doc('File')
|
||||
file_doc.content = z.read(file.filename)
|
||||
file_doc.file_name = filename
|
||||
file_doc.folder = self.folder
|
||||
file_doc.is_private = self.is_private
|
||||
file_doc.attached_to_doctype = self.attached_to_doctype
|
||||
file_doc.attached_to_name = self.attached_to_name
|
||||
file_doc.save()
|
||||
files.append(file_doc)
|
||||
|
||||
frappe.delete_doc('File', self.name)
|
||||
return files
|
||||
|
|
@ -359,6 +360,9 @@ class File(Document):
|
|||
"""write file to disk with a random name (to compare)"""
|
||||
file_path = get_files_path(is_private=self.is_private)
|
||||
|
||||
if os.path.sep in self.file_name:
|
||||
frappe.throw(_('File name cannot have {0}').format(os.path.sep))
|
||||
|
||||
# create directory (if not exists)
|
||||
frappe.create_folder(file_path)
|
||||
# write the file
|
||||
|
|
@ -938,7 +942,7 @@ def attach_files_to_document(doc, event):
|
|||
# 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")):
|
||||
if not (value or '').startswith(("/files", "/private/files")):
|
||||
return
|
||||
|
||||
if frappe.db.exists("File", {
|
||||
|
|
|
|||
|
|
@ -54,12 +54,12 @@
|
|||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-05-04 11:05:54.750351",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Has Domain",
|
||||
"name_case": "",
|
||||
"owner": "makarand@erpnext.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldtype",
|
||||
"options": "Check\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime",
|
||||
"options": "Check\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nInt\nLink\nSelect\nTime",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-17 14:32:17.174796",
|
||||
"modified": "2020-09-03 10:52:03.895817",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Report Column",
|
||||
|
|
|
|||
|
|
@ -109,12 +109,14 @@ class ScheduledJobType(Document):
|
|||
def on_trash(self):
|
||||
frappe.db.sql('delete from `tabScheduled Job Log` where scheduled_job_type=%s', self.name)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_event(doc):
|
||||
frappe.only_for('System Manager')
|
||||
doc = json.loads(doc)
|
||||
frappe.get_doc('Scheduled Job Type', doc.get('name')).enqueue()
|
||||
|
||||
|
||||
def run_scheduled_job(job_type):
|
||||
'''This is a wrapper function that runs a hooks.scheduler_events method'''
|
||||
try:
|
||||
|
|
@ -122,44 +124,62 @@ def run_scheduled_job(job_type):
|
|||
except Exception:
|
||||
print(frappe.get_traceback())
|
||||
|
||||
def sync_jobs():
|
||||
frappe.reload_doc('core', 'doctype', 'scheduled_job_type')
|
||||
all_events = []
|
||||
scheduler_events = frappe.get_hooks("scheduler_events")
|
||||
insert_events(all_events, scheduler_events)
|
||||
clear_events(all_events, scheduler_events)
|
||||
|
||||
def insert_events(all_events, scheduler_events):
|
||||
def sync_jobs(hooks=None):
|
||||
frappe.reload_doc("core", "doctype", "scheduled_job_type")
|
||||
scheduler_events = hooks or frappe.get_hooks("scheduler_events")
|
||||
all_events = insert_events(scheduler_events)
|
||||
clear_events(all_events)
|
||||
|
||||
|
||||
def insert_events(scheduler_events):
|
||||
cron_jobs, event_jobs = [], []
|
||||
for event_type in scheduler_events:
|
||||
events = scheduler_events.get(event_type)
|
||||
if isinstance(events, dict):
|
||||
insert_cron_event(events, all_events)
|
||||
cron_jobs += insert_cron_jobs(events)
|
||||
else:
|
||||
# hourly, daily etc
|
||||
insert_event_list(events, event_type, all_events)
|
||||
event_jobs += insert_event_jobs(events, event_type)
|
||||
return cron_jobs + event_jobs
|
||||
|
||||
def insert_cron_event(events, all_events):
|
||||
|
||||
def insert_cron_jobs(events):
|
||||
cron_jobs = []
|
||||
for cron_format in events:
|
||||
for event in events.get(cron_format):
|
||||
all_events.append(event)
|
||||
insert_single_event('Cron', event, cron_format)
|
||||
cron_jobs.append(event)
|
||||
insert_single_event("Cron", event, cron_format)
|
||||
return cron_jobs
|
||||
|
||||
def insert_event_list(events, event_type, all_events):
|
||||
|
||||
def insert_event_jobs(events, event_type):
|
||||
event_jobs = []
|
||||
for event in events:
|
||||
all_events.append(event)
|
||||
event_jobs.append(event)
|
||||
frequency = event_type.replace('_', ' ').title()
|
||||
insert_single_event(frequency, event)
|
||||
return event_jobs
|
||||
|
||||
def insert_single_event(frequency, event, cron_format = None):
|
||||
if not frappe.db.exists('Scheduled Job Type', dict(method=event)):
|
||||
frappe.get_doc(dict(
|
||||
doctype = 'Scheduled Job Type',
|
||||
method = event,
|
||||
cron_format = cron_format,
|
||||
frequency = frequency
|
||||
)).insert()
|
||||
|
||||
def clear_events(all_events, scheduler_events):
|
||||
for event in frappe.get_all('Scheduled Job Type', ('name', 'method')):
|
||||
def insert_single_event(frequency, event, cron_format=None):
|
||||
cron_expr = {"cron_format": cron_format} if cron_format else {}
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Scheduled Job Type",
|
||||
"method": event,
|
||||
"cron_format": cron_format,
|
||||
"frequency": frequency
|
||||
})
|
||||
|
||||
if not frappe.db.exists("Scheduled Job Type", {"method": event, "frequency": frequency, **cron_expr }):
|
||||
try:
|
||||
doc.insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
doc.delete()
|
||||
doc.insert()
|
||||
|
||||
|
||||
def clear_events(all_events):
|
||||
for event in frappe.get_all("Scheduled Job Type", ("name", "method")):
|
||||
if event.method not in all_events:
|
||||
frappe.delete_doc('Scheduled Job Type', event.name)
|
||||
frappe.delete_doc("Scheduled Job Type", event.name)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@ from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
|
|||
|
||||
class TestScheduledJobType(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not frappe.get_all('Scheduled Job Type', limit=1):
|
||||
frappe.db.rollback()
|
||||
frappe.db.sql('truncate `tabScheduled Job Type`')
|
||||
sync_jobs()
|
||||
frappe.db.commit()
|
||||
frappe.db.rollback()
|
||||
frappe.db.sql('truncate `tabScheduled Job Type`')
|
||||
sync_jobs()
|
||||
frappe.db.commit()
|
||||
|
||||
def test_sync_jobs(self):
|
||||
all_job = frappe.get_doc('Scheduled Job Type',
|
||||
|
|
@ -32,6 +31,12 @@ class TestScheduledJobType(unittest.TestCase):
|
|||
self.assertEqual(cron_job.frequency, 'Cron')
|
||||
self.assertEqual(cron_job.cron_format, '0/15 * * * *')
|
||||
|
||||
# check if jobs are synced after change in hooks
|
||||
updated_scheduler_events = { "hourly": ["frappe.email.queue.flush"] }
|
||||
sync_jobs(updated_scheduler_events)
|
||||
updated_scheduled_job = frappe.get_doc("Scheduled Job Type", {"method": "frappe.email.queue.flush"})
|
||||
self.assertEqual(updated_scheduled_job.frequency, "Hourly")
|
||||
|
||||
def test_daily_job(self):
|
||||
job = frappe.get_doc('Scheduled Job Type', dict(method = 'frappe.email.queue.clear_outbox'))
|
||||
job.db_set('last_execution', '2019-01-01 00:00:00')
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"script_type",
|
||||
"disabled",
|
||||
"column_break_3",
|
||||
"reference_doctype",
|
||||
"doctype_event",
|
||||
"api_method",
|
||||
"allow_guest",
|
||||
"column_break_3",
|
||||
"disabled",
|
||||
"section_break_8",
|
||||
"script",
|
||||
"help_section",
|
||||
|
|
@ -85,8 +85,9 @@
|
|||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-07 13:13:02.483963",
|
||||
"modified": "2020-08-24 16:44:41.060350",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Server Script",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ class ServerScript(Document):
|
|||
# validate if guest is allowed
|
||||
if frappe.session.user == 'Guest' and not self.allow_guest:
|
||||
raise frappe.PermissionError
|
||||
safe_exec(self.script)
|
||||
_globals, _locals = safe_exec(self.script)
|
||||
return _globals.frappe.flags # output can be stored in flags
|
||||
else:
|
||||
# wrong report type!
|
||||
raise frappe.DoesNotExistError
|
||||
|
|
|
|||
|
|
@ -36,6 +36,15 @@ if "validate" in doc.description:
|
|||
allow_guest = 1,
|
||||
script = '''
|
||||
frappe.response['message'] = 'hello'
|
||||
'''
|
||||
),
|
||||
dict(
|
||||
name='test_return_value',
|
||||
script_type = 'API',
|
||||
api_method = 'test_return_value',
|
||||
allow_guest = 1,
|
||||
script = '''
|
||||
frappe.flags = 'hello'
|
||||
'''
|
||||
)
|
||||
]
|
||||
|
|
@ -73,3 +82,6 @@ class TestServerScript(unittest.TestCase):
|
|||
response = requests.post(get_site_url(frappe.local.site) + "/api/method/test_server_script")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual("hello", response.json()["message"])
|
||||
|
||||
def test_api_return(self):
|
||||
self.assertEqual(frappe.get_doc('Server Script', 'test_return_value').execute_method(), 'hello')
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"password_settings",
|
||||
"logout_on_password_reset",
|
||||
"force_user_to_reset_password",
|
||||
"password_reset_limit",
|
||||
"column_break_31",
|
||||
"enable_password_policy",
|
||||
"minimum_password_score",
|
||||
|
|
@ -415,6 +416,13 @@
|
|||
"fieldtype": "Int",
|
||||
"label": "Run Jobs only Daily if Inactive For (Days)"
|
||||
},
|
||||
{
|
||||
"default": "3",
|
||||
"description": "Hourly rate limit for generating password reset links",
|
||||
"fieldname": "password_reset_limit",
|
||||
"fieldtype": "Int",
|
||||
"label": "Password Reset Link Generation Limit"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "logout_on_password_reset",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class TestUser(unittest.TestCase):
|
|||
# disable password strength test
|
||||
frappe.db.set_value("System Settings", "System Settings", "enable_password_policy", 0)
|
||||
frappe.db.set_value("System Settings", "System Settings", "minimum_password_score", "")
|
||||
frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 3)
|
||||
|
||||
def test_user_type(self):
|
||||
new_user = frappe.get_doc(dict(doctype='User', email='test-for-type@example.com',
|
||||
|
|
@ -222,6 +223,19 @@ class TestUser(unittest.TestCase):
|
|||
self.assertEqual(extract_mentions(comment)[0], "test_user@example.com")
|
||||
self.assertEqual(extract_mentions(comment)[1], "test.again@example1.com")
|
||||
|
||||
def test_rate_limiting_for_reset_password(self):
|
||||
from frappe.utils.password import delete_password_reset_cache
|
||||
delete_password_reset_cache()
|
||||
|
||||
frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 1)
|
||||
|
||||
user = frappe.get_doc("User", "testperm@example.com")
|
||||
link = user.reset_password()
|
||||
self.assertRegex(link, "\/update-password\?key=[A-Za-z0-9]*")
|
||||
|
||||
self.assertRaises(frappe.ValidationError, user.reset_password, False)
|
||||
|
||||
|
||||
def delete_contact(user):
|
||||
frappe.db.sql("DELETE FROM `tabContact` WHERE `email_id`= %s", user)
|
||||
frappe.db.sql("DELETE FROM `tabContact Email` WHERE `email_id`= %s", user)
|
||||
|
|
|
|||
|
|
@ -357,7 +357,7 @@
|
|||
"depends_on": "enabled",
|
||||
"fieldname": "email_settings",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Settings"
|
||||
"label": "Email"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
|
|
@ -383,12 +383,6 @@
|
|||
"label": "Email Signature",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "email_inbox",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Inbox"
|
||||
},
|
||||
{
|
||||
"fieldname": "user_emails",
|
||||
"fieldtype": "Table",
|
||||
|
|
@ -670,7 +664,7 @@
|
|||
}
|
||||
],
|
||||
"max_attachments": 5,
|
||||
"modified": "2020-08-06 19:48:49.677800",
|
||||
"modified": "2020-08-26 19:48:49.677800",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "User",
|
||||
|
|
@ -704,4 +698,4 @@
|
|||
"sort_order": "DESC",
|
||||
"title_field": "full_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,15 +13,16 @@ from frappe.utils.user import get_system_managers
|
|||
from bs4 import BeautifulSoup
|
||||
import frappe.permissions
|
||||
import frappe.share
|
||||
import re
|
||||
import json
|
||||
|
||||
from frappe.website.utils import is_signup_enabled
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
|
||||
STANDARD_USERS = ("Guest", "Administrator")
|
||||
|
||||
class MaxUsersReachedError(frappe.ValidationError): pass
|
||||
|
||||
class MaxUsersReachedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class User(Document):
|
||||
__new_password = None
|
||||
|
|
@ -225,6 +226,11 @@ class User(Document):
|
|||
def reset_password(self, send_email=False, password_expired=False):
|
||||
from frappe.utils import random_string, get_url
|
||||
|
||||
rate_limit = frappe.db.get_single_value("System Settings", "password_reset_limit")
|
||||
|
||||
if rate_limit:
|
||||
check_password_reset_limit(self.name, rate_limit)
|
||||
|
||||
key = random_string(32)
|
||||
self.db_set("reset_password_key", key)
|
||||
|
||||
|
|
@ -236,6 +242,7 @@ class User(Document):
|
|||
if send_email:
|
||||
self.password_reset_mail(link)
|
||||
|
||||
update_password_reset_limit(self.name)
|
||||
return link
|
||||
|
||||
def get_other_system_managers(self):
|
||||
|
|
@ -1113,4 +1120,17 @@ def generate_keys(user):
|
|||
@frappe.whitelist()
|
||||
def switch_theme(theme):
|
||||
if theme in ["Dark", "Light"]:
|
||||
frappe.db.set_value("User", frappe.session.user, "desk_theme", theme)
|
||||
frappe.db.set_value("User", frappe.session.user, "desk_theme", theme)
|
||||
|
||||
def update_password_reset_limit(user):
|
||||
generated_link_count = get_generated_link_count(user)
|
||||
generated_link_count += 1
|
||||
frappe.cache().hset("password_reset_link_count", user, generated_link_count)
|
||||
|
||||
def check_password_reset_limit(user, rate_limit):
|
||||
generated_link_count = get_generated_link_count(user)
|
||||
if generated_link_count >= rate_limit:
|
||||
frappe.throw(_("You have reached the hourly limit for generating password reset links. Please try again later."))
|
||||
|
||||
def get_generated_link_count(user):
|
||||
return cint(frappe.cache().hget("password_reset_link_count", user)) or 0
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ class TestUserPermission(unittest.TestCase):
|
|||
user = create_user('test_user_perm1@example.com', 'Website Manager')
|
||||
for category in ['general', 'public']:
|
||||
if not frappe.db.exists('Blog Category', category):
|
||||
frappe.get_doc({'doctype': 'Blog Category',
|
||||
'category_name': category, 'title': category}).insert()
|
||||
frappe.get_doc({'doctype': 'Blog Category', 'title': category}).insert()
|
||||
|
||||
param = get_params(user, 'Blog Category', 'general', is_default=1)
|
||||
add_user_permissions(param)
|
||||
|
|
|
|||
|
|
@ -58,382 +58,384 @@
|
|||
],
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Document",
|
||||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"bold": 1,
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Document",
|
||||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_filter": 1,
|
||||
"label": "Label",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data"
|
||||
"bold": 1,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_filter": 1,
|
||||
"label": "Label",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "label_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Label Help",
|
||||
"oldfieldtype": "HTML"
|
||||
"fieldname": "label_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Label Help",
|
||||
"oldfieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Fieldname",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Select the label after which you want to insert new field.",
|
||||
"fieldname": "insert_after",
|
||||
"fieldtype": "Select",
|
||||
"label": "Insert After",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "insert_after",
|
||||
"oldfieldtype": "Select"
|
||||
"description": "Select the label after which you want to insert new field.",
|
||||
"fieldname": "insert_after",
|
||||
"fieldtype": "Select",
|
||||
"label": "Insert After",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "insert_after",
|
||||
"oldfieldtype": "Select"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Field Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
|
||||
"reqd": 1
|
||||
"bold": 1,
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Field Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
|
||||
},
|
||||
{
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch If Empty"
|
||||
"default": "0",
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch If Empty"
|
||||
},
|
||||
{
|
||||
"fieldname": "options_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Options Help",
|
||||
"oldfieldtype": "HTML"
|
||||
"fieldname": "options_help",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Options Help",
|
||||
"oldfieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On"
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On"
|
||||
},
|
||||
{
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"label": "Default Value",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"label": "Default Value",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On",
|
||||
"length": 255
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Field Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Field Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"label": "Permission Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int"
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"label": "Permission Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int"
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Width",
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data"
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Width",
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "properties",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"print_width": "50%",
|
||||
"width": "50%"
|
||||
"fieldname": "properties",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"print_width": "50%",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Mandatory Field",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Mandatory Field",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only"
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype===\"Link\"",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype===\"Link\"",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Print Width",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Print Width",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "no_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "No Copy",
|
||||
"oldfieldname": "no_copy",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "no_copy",
|
||||
"fieldtype": "Check",
|
||||
"label": "No Copy",
|
||||
"oldfieldname": "no_copy",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Standard Filter"
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Standard Filter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "search_index",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Index",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"default": "0",
|
||||
"fieldname": "search_index",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Index",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
"default": "0",
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore XSS Filter"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
},
|
||||
{
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"length": 255
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"length": 255
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"length": 255
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-06 23:43:00.123575",
|
||||
"modified": "2020-08-28 11:28:44.377753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "dt,label,fieldtype,options",
|
||||
|
|
|
|||
|
|
@ -1,187 +1,91 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:01",
|
||||
"custom": 0,
|
||||
"description": "Adds a client custom script to a DocType",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"creation": "2013-01-10 16:34:01",
|
||||
"description": "Adds a client custom script to a DocType",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dt",
|
||||
"enabled",
|
||||
"script",
|
||||
"sample"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "DocType",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"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,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "DocType",
|
||||
"oldfieldname": "dt",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "script",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Script",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "script",
|
||||
"oldfieldtype": "Code",
|
||||
"options": "JS",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "script",
|
||||
"fieldtype": "Code",
|
||||
"label": "Script",
|
||||
"oldfieldname": "script",
|
||||
"oldfieldtype": "Code",
|
||||
"options": "JS",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "sample",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Sample",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "<h3>Custom Script Help</h3>\n<p>Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started</p>\n<pre><code>\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname); \ncur_frm.add_fetch('customer', 'local_tax_no', 'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task', 'validate', function(frm) {\n if (frm.doc.from_date < get_today()) {\n msgprint('You can not select past date in From Date');\n validated = false;\n } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task', {\n refresh: function(frm) {\n // use the __islocal value of doc, to check if the doc is saved or not\n frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);\n } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task', {\n validate: function(frm) {\n if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {\n msgprint('You are only allowed Material Receipt');\n validated = false;\n }\n } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice', {\n validate: function(frm) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(frm.doc.sales_team, function(i, d) {\n // calculate incentive\n var incentive_percent = 2;\n if(frm.doc.base_grand_total > 400) incentive_percent = 4;\n // actual incentive\n d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n frm.doc.total_incentive = total_incentive;\n } \n})\n\n</code></pre>",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "sample",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Sample",
|
||||
"options": "<h3>Custom Script Help</h3>\n<p>Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started</p>\n<pre><code>\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname); \ncur_frm.add_fetch('customer', 'local_tax_no', 'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task', 'validate', function(frm) {\n if (frm.doc.from_date < get_today()) {\n msgprint('You can not select past date in From Date');\n validated = false;\n } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task', {\n refresh: function(frm) {\n // use the __islocal value of doc, to check if the doc is saved or not\n frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);\n } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task', {\n validate: function(frm) {\n if(user=='user1@example.com' && frm.doc.purpose!='Material Receipt') {\n msgprint('You are only allowed Material Receipt');\n validated = false;\n }\n } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice', {\n validate: function(frm) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(frm.doc.sales_team, function(i, d) {\n // calculate incentive\n var incentive_percent = 2;\n if(frm.doc.base_grand_total > 400) incentive_percent = 4;\n // actual incentive\n d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n frm.doc.total_incentive = total_incentive;\n } \n})\n\n</code></pre>",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enabled",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2019-03-21 14:26:57.402994",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Script",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-glass",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-24 21:56:07.719579",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Script",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 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
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 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
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
||||
|
|
@ -60,364 +60,366 @@
|
|||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "label_and_type",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Label and Type"
|
||||
"fieldname": "label_and_type",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Label and Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data",
|
||||
"search_index": 1
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Label",
|
||||
"oldfieldname": "label",
|
||||
"oldfieldtype": "Data",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"default": "Data",
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"oldfieldname": "fieldtype",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Name",
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Name",
|
||||
"oldfieldname": "fieldname",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mandatory",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mandatory",
|
||||
"oldfieldname": "reqd",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
"default": "0",
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"label": "Unique"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
"default": "0",
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "In List View"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Standard Filter"
|
||||
"default": "0",
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Standard Filter"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
"default": "0",
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Global Search"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
"default": "0",
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bold"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"label": "Translatable"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"label": "Precision",
|
||||
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Length"
|
||||
},
|
||||
{
|
||||
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Options",
|
||||
"oldfieldname": "options",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Fetch From"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch If Empty"
|
||||
"default": "0",
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Fetch If Empty"
|
||||
},
|
||||
{
|
||||
"fieldname": "permissions",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Permissions"
|
||||
"fieldname": "permissions",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Permissions"
|
||||
},
|
||||
{
|
||||
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On",
|
||||
"oldfieldname": "depends_on",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "JS"
|
||||
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Depends On",
|
||||
"oldfieldname": "depends_on",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Perm Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int"
|
||||
"default": "0",
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Perm Level",
|
||||
"oldfieldname": "permlevel",
|
||||
"oldfieldtype": "Int"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden",
|
||||
"oldfieldname": "hidden",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
"default": "0",
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hidden",
|
||||
"oldfieldname": "hidden",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only"
|
||||
"default": "0",
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"label": "Read Only"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"label": "Collapsible"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype == \"Table\"",
|
||||
"fieldname": "allow_bulk_edit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Bulk Edit"
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.fieldtype == \"Table\"",
|
||||
"fieldname": "allow_bulk_edit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Bulk Edit"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On",
|
||||
"options": "JS"
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Collapsible Depends On",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
"default": "0",
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore User Permissions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow on Submit",
|
||||
"oldfieldname": "allow_on_submit",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Report Hide",
|
||||
"oldfieldname": "report_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.fieldtype == 'Link')",
|
||||
"fieldname": "remember_last_selected_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Remember Last Selected Value"
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.fieldtype == 'Link')",
|
||||
"fieldname": "remember_last_selected_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Remember Last Selected Value"
|
||||
},
|
||||
{
|
||||
"fieldname": "display",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Display"
|
||||
"fieldname": "display",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Display"
|
||||
},
|
||||
{
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"label": "Default",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"label": "Default",
|
||||
"oldfieldname": "default",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Filter",
|
||||
"oldfieldname": "in_filter",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
"default": "0",
|
||||
"fieldname": "in_filter",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Filter",
|
||||
"oldfieldname": "in_filter",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
"print_width": "300px",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check"
|
||||
"default": "0",
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide",
|
||||
"oldfieldname": "print_hide",
|
||||
"oldfieldtype": "Check"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
"default": "0",
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"label": "Print Hide If No Value"
|
||||
},
|
||||
{
|
||||
"description": "Print Width of the field, if the field is a column in a table",
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Print Width",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
"description": "Print Width of the field, if the field is a column in a table",
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"label": "Print Width",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:cur_frm.doc.istable",
|
||||
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
"depends_on": "eval:cur_frm.doc.istable",
|
||||
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"label": "Columns"
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Width",
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Width",
|
||||
"oldfieldname": "width",
|
||||
"oldfieldtype": "Data",
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_custom_field",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Custom Field",
|
||||
"read_only": 1
|
||||
"default": "0",
|
||||
"fieldname": "is_custom_field",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Custom Field",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
"default": "0",
|
||||
"fieldname": "allow_in_quick_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow in Quick Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "property_depends_on_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Property Depends On"
|
||||
"fieldname": "property_depends_on_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Property Depends On"
|
||||
},
|
||||
{
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"options": "JS"
|
||||
"fieldname": "mandatory_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Mandatory Depends On",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_33",
|
||||
"fieldtype": "Column Break"
|
||||
"fieldname": "column_break_33",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"options": "JS"
|
||||
"fieldname": "read_only_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"label": "Read Only Depends On",
|
||||
"options": "JS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
"default": "0",
|
||||
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
|
||||
"fieldname": "in_preview",
|
||||
"fieldtype": "Check",
|
||||
"label": "In Preview"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_seconds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Seconds"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Duration'",
|
||||
"fieldname": "hide_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Days"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.fieldtype=='Section Break'",
|
||||
"fieldname": "hide_border",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Border"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-02 23:45:46.810868",
|
||||
"modified": "2020-08-28 11:28:59.084060",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ def export_package():
|
|||
@frappe.whitelist()
|
||||
def import_package(package=None):
|
||||
"""Import package from JSON."""
|
||||
frappe.only_for("System Manager")
|
||||
if isinstance(package, string_types):
|
||||
package = json.loads(package)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_source_value
|
||||
from frappe.utils.safe_exec import get_safe_globals
|
||||
|
||||
class DataMigrationMapping(Document):
|
||||
def get_filters(self):
|
||||
if self.condition:
|
||||
return frappe.safe_eval(self.condition, dict(frappe=frappe))
|
||||
return frappe.safe_eval(self.condition, get_safe_globals())
|
||||
|
||||
def get_fields(self):
|
||||
fields = []
|
||||
|
|
@ -64,9 +64,16 @@ def get_value_from_fieldname(field_map, fieldname_field, doc):
|
|||
field_name = get_source_value(field_map, fieldname_field)
|
||||
|
||||
if field_name.startswith('eval:'):
|
||||
value = frappe.safe_eval(field_name[5:], dict(frappe=frappe))
|
||||
value = frappe.safe_eval(field_name[5:], get_safe_globals())
|
||||
elif field_name[0] in ('"', "'"):
|
||||
value = field_name[1:-1]
|
||||
else:
|
||||
value = get_source_value(doc, field_name)
|
||||
return value
|
||||
|
||||
def get_source_value(source, key):
|
||||
'''Get value from source (object or dict) based on key'''
|
||||
if isinstance(source, dict):
|
||||
return source.get(key)
|
||||
else:
|
||||
return getattr(source, key)
|
||||
|
|
|
|||
|
|
@ -186,8 +186,8 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-28 15:49:54.019073",
|
||||
"modified_by": "cave@aperture.com",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Data Migration",
|
||||
"name": "Data Migration Plan",
|
||||
"name_case": "",
|
||||
|
|
|
|||
|
|
@ -800,12 +800,12 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-07-30 07:02:26.980372",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Data Migration",
|
||||
"name": "Data Migration Run",
|
||||
"name_case": "",
|
||||
"owner": "faris@erpnext.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ from __future__ import unicode_literals
|
|||
import frappe, json, math
|
||||
from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from frappe.utils import get_source_value, cstr
|
||||
from frappe.utils import cstr
|
||||
from frappe.data_migration.doctype.data_migration_mapping.data_migration_mapping import get_source_value
|
||||
|
||||
class DataMigrationRun(Document):
|
||||
def run(self):
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ CREATE TABLE `tabDocType Action` (
|
|||
`label` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`group` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action_type` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action` varchar(140) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`action` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`name`),
|
||||
KEY `parent` (`parent`),
|
||||
KEY `modified` (`modified`)
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ def bootstrap_database(db_name, verbose, source_sql=None):
|
|||
sys.exit(1)
|
||||
|
||||
import_db_from_sql(source_sql, verbose)
|
||||
|
||||
frappe.connect(db_name=db_name)
|
||||
if not 'tabDefaultValue' in frappe.db.get_tables():
|
||||
print('''Database not installed, this can due to lack of permission, or that the database name exists.
|
||||
Check your mysql root password, or use --force to reinstall''')
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ CREATE TABLE "tabDocType Action" (
|
|||
"parenttype" varchar(255) DEFAULT NULL,
|
||||
"idx" bigint NOT NULL DEFAULT 0,
|
||||
"label" varchar(140) NOT NULL,
|
||||
"group" varchar(140) DEFAULT NULL,
|
||||
"group" text DEFAULT NULL,
|
||||
"action_type" varchar(140) NOT NULL,
|
||||
"action" varchar(140) NOT NULL,
|
||||
PRIMARY KEY ("name")
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class PostgresTable(DBTable):
|
|||
elif col.fieldtype in ("Check"):
|
||||
using_clause = "USING {}::smallint".format(col.fieldname)
|
||||
|
||||
query.append("ALTER COLUMN {0} TYPE {1} {2}".format(
|
||||
query.append("ALTER COLUMN `{0}` TYPE {1} {2}".format(
|
||||
col.fieldname,
|
||||
get_definition(col.fieldtype, precision=col.precision, length=col.length),
|
||||
using_clause)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class Workspace:
|
|||
|
||||
self.doc = self.get_page_for_user()
|
||||
|
||||
if self.doc.module not in self.allowed_modules:
|
||||
if self.doc.module and self.doc.module not in self.allowed_modules:
|
||||
raise frappe.PermissionError
|
||||
|
||||
self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items)
|
||||
|
|
@ -203,7 +203,7 @@ class Workspace:
|
|||
cards = cards + get_custom_reports_and_doctypes(self.doc.module)
|
||||
|
||||
if len(self.extended_cards):
|
||||
cards = cards + self.extended_cards
|
||||
cards = merge_cards_based_on_label(cards + self.extended_cards)
|
||||
default_country = frappe.db.get_default("country")
|
||||
|
||||
def _doctype_contains_a_record(name):
|
||||
|
|
@ -590,3 +590,16 @@ def reset_customization(page):
|
|||
original_page = frappe.get_doc("Desk Page", page)
|
||||
page_doc = get_custom_workspace_for_user(page)
|
||||
page_doc.delete()
|
||||
|
||||
def merge_cards_based_on_label(cards):
|
||||
"""Merge cards with common label."""
|
||||
cards_dict = {}
|
||||
for card in cards:
|
||||
if card.label in cards_dict:
|
||||
links = loads(cards_dict[card.label].links) + loads(card.links)
|
||||
cards_dict[card.label].update(dict(links=dumps(links)))
|
||||
cards_dict[card.label] = cards_dict.pop(card.label)
|
||||
else:
|
||||
cards_dict[card.label] = card
|
||||
|
||||
return list(cards_dict.values())
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@
|
|||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-06-15 11:24:57.639430",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Calendar View",
|
||||
"owner": "faris@erpnext.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
|
|
|
|||
0
frappe/desk/doctype/console_log/__init__.py
Normal file
0
frappe/desk/doctype/console_log/__init__.py
Normal file
8
frappe/desk/doctype/console_log/console_log.js
Normal file
8
frappe/desk/doctype/console_log/console_log.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('Console Log', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
52
frappe/desk/doctype/console_log/console_log.json
Normal file
52
frappe/desk/doctype/console_log/console_log.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"actions": [],
|
||||
"autoname": "format:Log on {timestamp}",
|
||||
"creation": "2020-08-18 19:56:12.336427",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"script",
|
||||
"output"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "script",
|
||||
"fieldtype": "Code",
|
||||
"in_list_view": 1,
|
||||
"label": "Script",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "output",
|
||||
"fieldtype": "Code",
|
||||
"label": "Output",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-18 20:07:57.587344",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Console Log",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
10
frappe/desk/doctype/console_log/console_log.py
Normal file
10
frappe/desk/doctype/console_log/console_log.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 ConsoleLog(Document):
|
||||
pass
|
||||
10
frappe/desk/doctype/console_log/test_console_log.py
Normal file
10
frappe/desk/doctype/console_log/test_console_log.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 TestConsoleLog(unittest.TestCase):
|
||||
pass
|
||||
|
|
@ -60,11 +60,11 @@ def has_permission(doc, ptype, user):
|
|||
|
||||
|
||||
if doc.chart_type == 'Report':
|
||||
allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()])
|
||||
allowed_reports = [key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
|
||||
if doc.report_name in allowed_reports:
|
||||
return True
|
||||
else:
|
||||
allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read())
|
||||
allowed_doctypes = [frappe.permissions.get_doctypes_with_read()]
|
||||
if doc.document_type in allowed_doctypes:
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class DeskPage(Document):
|
|||
|
||||
pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1)
|
||||
|
||||
return { page[1]: page[0] for page in pages }
|
||||
return { page[1]: page[0] for page in pages if page[1] }
|
||||
|
||||
def disable_saving_as_standard():
|
||||
return frappe.flags.in_install or \
|
||||
|
|
|
|||
|
|
@ -120,8 +120,8 @@
|
|||
"hide_toolbar": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-31 22:31:12.886950",
|
||||
"modified_by": "umair@erpnext.com",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Notification Log",
|
||||
"owner": "Administrator",
|
||||
|
|
|
|||
0
frappe/desk/doctype/system_console/__init__.py
Normal file
0
frappe/desk/doctype/system_console/__init__.py
Normal file
21
frappe/desk/doctype/system_console/system_console.js
Normal file
21
frappe/desk/doctype/system_console/system_console.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2020, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('System Console', {
|
||||
onload: function(frm) {
|
||||
frappe.ui.keys.add_shortcut({
|
||||
shortcut: 'shift+enter',
|
||||
action: () => frm.execute_action('Execute'),
|
||||
page: frm.page,
|
||||
description: __('Execute Console script'),
|
||||
ignore_inputs: true,
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.page.set_primary_action(__("Execute"), () => {
|
||||
frm.execute_action('Execute');
|
||||
});
|
||||
}
|
||||
});
|
||||
68
frappe/desk/doctype/system_console/system_console.json
Normal file
68
frappe/desk/doctype/system_console/system_console.json
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"actions": [
|
||||
{
|
||||
"action": "#List/Console Log/List",
|
||||
"action_type": "Route",
|
||||
"label": "Logs"
|
||||
},
|
||||
{
|
||||
"action": "frappe.desk.doctype.system_console.system_console.execute_code",
|
||||
"action_type": "Server Action",
|
||||
"hidden": 1,
|
||||
"label": "Execute"
|
||||
}
|
||||
],
|
||||
"creation": "2020-08-18 17:44:35.647815",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"console",
|
||||
"commit",
|
||||
"output"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"description": "To print output use <code>log(text)</code>",
|
||||
"fieldname": "console",
|
||||
"fieldtype": "Code",
|
||||
"label": "Console",
|
||||
"options": "Python"
|
||||
},
|
||||
{
|
||||
"fieldname": "output",
|
||||
"fieldtype": "Code",
|
||||
"label": "Output",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "commit",
|
||||
"fieldtype": "Check",
|
||||
"label": "Commit"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-21 14:44:35.296877",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "System Console",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"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
|
||||
}
|
||||
38
frappe/desk/doctype/system_console/system_console.py
Normal file
38
frappe/desk/doctype/system_console/system_console.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.utils.safe_exec import safe_exec
|
||||
from frappe.model.document import Document
|
||||
|
||||
class SystemConsole(Document):
|
||||
def run(self):
|
||||
frappe.only_for('System Manager')
|
||||
try:
|
||||
frappe.debug_log = []
|
||||
safe_exec(self.console)
|
||||
self.output = '\n'.join(frappe.debug_log)
|
||||
except: # noqa: E722
|
||||
self.output = frappe.get_traceback()
|
||||
|
||||
if self.commit:
|
||||
frappe.db.commit()
|
||||
else:
|
||||
frappe.db.rollback()
|
||||
|
||||
frappe.get_doc(dict(
|
||||
doctype='Console Log',
|
||||
script=self.console,
|
||||
output=self.output)).insert()
|
||||
frappe.db.commit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_code(doc):
|
||||
console = frappe.get_doc(json.loads(doc))
|
||||
console.run()
|
||||
return console.as_dict()
|
||||
20
frappe/desk/doctype/system_console/test_system_console.py
Normal file
20
frappe/desk/doctype/system_console/test_system_console.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
|
||||
class TestSystemConsole(unittest.TestCase):
|
||||
def test_system_console(self):
|
||||
system_console = frappe.get_doc('System Console')
|
||||
system_console.console = 'log("hello")'
|
||||
system_console.run()
|
||||
|
||||
self.assertEqual(system_console.output, 'hello')
|
||||
|
||||
system_console.console = 'log(frappe.db.get_value("DocType", "DocType", "module"))'
|
||||
system_console.run()
|
||||
|
||||
self.assertEqual(system_console.output, 'Core')
|
||||
|
|
@ -169,16 +169,14 @@ def get_comments(doctype, doc_name, frequency, user):
|
|||
return timeline
|
||||
|
||||
def is_document_followed(doctype, doc_name, user):
|
||||
docs = frappe.get_all(
|
||||
return frappe.db.exists(
|
||||
"Document Follow",
|
||||
filters={
|
||||
{
|
||||
"ref_doctype": doctype,
|
||||
"ref_docname": doc_name,
|
||||
"user": user
|
||||
},
|
||||
limit=1
|
||||
}
|
||||
)
|
||||
return len(docs)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_follow_users(doctype, doc_name):
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class FormMeta(Meta):
|
|||
def add_custom_script(self):
|
||||
"""embed all require files"""
|
||||
# custom script
|
||||
custom = frappe.db.get_value("Custom Script", {"dt": self.name}, "script") or ""
|
||||
custom = frappe.db.get_value("Custom Script", {"dt": self.name, "enabled": 1}, "script") or ""
|
||||
|
||||
self.set("__custom_js", custom)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ def savedocs(doc, action):
|
|||
# update recent documents
|
||||
run_onload(doc)
|
||||
send_updated_docs(doc)
|
||||
|
||||
frappe.msgprint(frappe._("Saved"), indicator='green', alert=True)
|
||||
except Exception:
|
||||
frappe.errprint(frappe.utils.get_traceback())
|
||||
raise
|
||||
|
|
@ -36,6 +38,7 @@ def cancel(doctype=None, name=None, workflow_state_fieldname=None, workflow_stat
|
|||
doc.set(workflow_state_fieldname, workflow_state)
|
||||
doc.cancel()
|
||||
send_updated_docs(doc)
|
||||
frappe.msgprint(frappe._("Cancelled"), indicator='red', alert=True)
|
||||
|
||||
except Exception:
|
||||
frappe.errprint(frappe.utils.get_traceback())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import frappe
|
||||
from frappe.model import no_value_fields
|
||||
from frappe.model import no_value_fields, table_fields
|
||||
import json
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -9,11 +9,13 @@ def get_preview_data(doctype, docname):
|
|||
if not meta.show_preview_popup: return
|
||||
|
||||
preview_fields = [field.fieldname for field in meta.fields \
|
||||
if field.in_preview and field.fieldtype not in no_value_fields]
|
||||
if field.in_preview and field.fieldtype not in no_value_fields \
|
||||
and field.fieldtype not in table_fields]
|
||||
|
||||
# no preview fields defined, build list from mandatory fields
|
||||
if not preview_fields:
|
||||
preview_fields = [field.fieldname for field in meta.fields if field.reqd]
|
||||
preview_fields = [field.fieldname for field in meta.fields if field.reqd \
|
||||
and field.fieldtype not in table_fields]
|
||||
|
||||
title_field = meta.get_title_field()
|
||||
image_field = meta.image_field
|
||||
|
|
|
|||
|
|
@ -1,263 +0,0 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
out = frappe.response
|
||||
|
||||
from frappe.utils import cint
|
||||
import frappe.defaults
|
||||
from six import text_type
|
||||
|
||||
def get_sql_tables(q):
|
||||
if q.find('WHERE') != -1:
|
||||
tl = q.split('FROM')[1].split('WHERE')[0].split(',')
|
||||
elif q.find('GROUP BY') != -1:
|
||||
tl = q.split('FROM')[1].split('GROUP BY')[0].split(',')
|
||||
else:
|
||||
tl = q.split('FROM')[1].split('ORDER BY')[0].split(',')
|
||||
return [t.strip().strip('`')[3:] for t in tl]
|
||||
|
||||
def get_parent_dt(dt):
|
||||
pdt = ''
|
||||
if frappe.db.sql('select name from `tabDocType` where istable=1 and name=%s', dt):
|
||||
import frappe.model.meta
|
||||
return frappe.model.meta.get_parent_dt(dt)
|
||||
return pdt
|
||||
|
||||
def get_sql_meta(tl):
|
||||
std_columns = {
|
||||
'owner':('Owner', '', '', '100'),
|
||||
'creation':('Created on', 'Date', '', '100'),
|
||||
'modified':('Last modified on', 'Date', '', '100'),
|
||||
'modified_by':('Modified By', '', '', '100')
|
||||
}
|
||||
|
||||
meta = {}
|
||||
|
||||
for dt in tl:
|
||||
meta[dt] = std_columns.copy()
|
||||
|
||||
# for table doctype, the ID is the parent id
|
||||
pdt = get_parent_dt(dt)
|
||||
if pdt:
|
||||
meta[dt]['parent'] = ('ID', 'Link', pdt, '200')
|
||||
|
||||
# get the field properties from DocField
|
||||
res = frappe.db.sql("select fieldname, label, fieldtype, options, width \
|
||||
from tabDocField where parent=%s", dt)
|
||||
for r in res:
|
||||
if r[0]:
|
||||
meta[dt][r[0]] = (r[1], r[2], r[3], r[4]);
|
||||
|
||||
# name
|
||||
meta[dt]['name'] = ('ID', 'Link', dt, '200')
|
||||
|
||||
return meta
|
||||
|
||||
def add_match_conditions(q, tl):
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
sl = []
|
||||
for dt in tl:
|
||||
s = build_match_conditions(dt)
|
||||
if s:
|
||||
sl.append(s)
|
||||
|
||||
# insert the conditions
|
||||
if sl:
|
||||
condition_st = q.find('WHERE')!=-1 and ' AND ' or ' WHERE '
|
||||
condition_end = q.find('ORDER BY')!=-1 and 'ORDER BY' or 'LIMIT'
|
||||
condition_end = q.find('GROUP BY')!=-1 and 'GROUP BY' or condition_end
|
||||
|
||||
if q.find('ORDER BY')!=-1 or q.find('LIMIT')!=-1 or q.find('GROUP BY')!=-1: # if query continues beyond conditions
|
||||
q = q.split(condition_end)
|
||||
q = q[0] + condition_st + '(' + ' OR '.join(sl) + ') ' + condition_end + q[1]
|
||||
else:
|
||||
q = q + condition_st + '(' + ' OR '.join(sl) + ')'
|
||||
|
||||
return q
|
||||
|
||||
def guess_type(m):
|
||||
"""
|
||||
Returns fieldtype depending on the MySQLdb Description
|
||||
"""
|
||||
if frappe.db.is_type_number(m):
|
||||
return 'Currency'
|
||||
elif m in frappe.is_type_datetime(m):
|
||||
return 'Date'
|
||||
else:
|
||||
return 'Data'
|
||||
|
||||
def build_description_simple():
|
||||
colnames, coltypes, coloptions, colwidths = [], [], [], []
|
||||
|
||||
for m in frappe.db.get_description():
|
||||
colnames.append(m[0])
|
||||
coltypes.append(guess_type[m[1]])
|
||||
coloptions.append('')
|
||||
colwidths.append('100')
|
||||
|
||||
return colnames, coltypes, coloptions, colwidths
|
||||
|
||||
def build_description_standard(meta, tl):
|
||||
|
||||
desc = frappe.db.get_description()
|
||||
|
||||
colnames, coltypes, coloptions, colwidths = [], [], [], []
|
||||
|
||||
# merged metadata - used if we are unable to
|
||||
# get both the table name and field name from
|
||||
# the description - in case of joins
|
||||
merged_meta = {}
|
||||
for d in meta:
|
||||
merged_meta.update(meta[d])
|
||||
|
||||
for f in desc:
|
||||
fn, dt = f[0], ''
|
||||
if '.' in fn:
|
||||
dt, fn = fn.split('.')
|
||||
|
||||
if (not dt) and merged_meta.get(fn):
|
||||
# no "AS" given, find type from merged description
|
||||
|
||||
desc = merged_meta[fn]
|
||||
colnames.append(desc[0] or fn)
|
||||
coltypes.append(desc[1] or '')
|
||||
coloptions.append(desc[2] or '')
|
||||
colwidths.append(desc[3] or '100')
|
||||
|
||||
elif fn in meta.get(dt,{}):
|
||||
# type specified for a multi-table join
|
||||
# usually from Report Builder
|
||||
|
||||
desc = meta[dt][fn]
|
||||
colnames.append(desc[0] or fn)
|
||||
coltypes.append(desc[1] or '')
|
||||
coloptions.append(desc[2] or '')
|
||||
colwidths.append(desc[3] or '100')
|
||||
|
||||
else:
|
||||
# nothing found
|
||||
# guess
|
||||
colnames.append(fn)
|
||||
coltypes.append(guess_type(f[1]))
|
||||
coloptions.append('')
|
||||
colwidths.append('100')
|
||||
|
||||
return colnames, coltypes, coloptions, colwidths
|
||||
|
||||
@frappe.whitelist()
|
||||
def runquery(q='', ret=0, from_export=0):
|
||||
import frappe.utils
|
||||
|
||||
formatted = cint(frappe.form_dict.get('formatted'))
|
||||
|
||||
# CASE A: Simple Query
|
||||
# --------------------
|
||||
if frappe.form_dict.get('simple_query') or frappe.form_dict.get('is_simple'):
|
||||
if not q: q = frappe.form_dict.get('simple_query') or frappe.form_dict.get('query')
|
||||
if q.split()[0].lower() != 'select':
|
||||
raise Exception('Query must be a SELECT')
|
||||
|
||||
as_dict = cint(frappe.form_dict.get('as_dict'))
|
||||
res = frappe.db.sql(q, as_dict = as_dict, as_list = not as_dict, formatted=formatted)
|
||||
|
||||
# build colnames etc from metadata
|
||||
colnames, coltypes, coloptions, colwidths = [], [], [], []
|
||||
|
||||
# CASE B: Standard Query
|
||||
# -----------------------
|
||||
else:
|
||||
if not q: q = frappe.form_dict.get('query')
|
||||
|
||||
tl = get_sql_tables(q)
|
||||
meta = get_sql_meta(tl)
|
||||
|
||||
q = add_match_conditions(q, tl)
|
||||
|
||||
# replace special variables
|
||||
q = q.replace('__user', frappe.session.user)
|
||||
q = q.replace('__today', frappe.utils.nowdate())
|
||||
|
||||
res = frappe.db.sql(q, as_list=1, formatted=formatted)
|
||||
|
||||
colnames, coltypes, coloptions, colwidths = build_description_standard(meta, tl)
|
||||
|
||||
# run server script
|
||||
# -----------------
|
||||
style, header_html, footer_html, page_template = '', '', '', ''
|
||||
|
||||
out['colnames'] = colnames
|
||||
out['coltypes'] = coltypes
|
||||
out['coloptions'] = coloptions
|
||||
out['colwidths'] = colwidths
|
||||
out['header_html'] = header_html
|
||||
out['footer_html'] = footer_html
|
||||
out['page_template'] = page_template
|
||||
|
||||
if style:
|
||||
out['style'] = style
|
||||
|
||||
# just the data - return
|
||||
if ret==1:
|
||||
return res
|
||||
|
||||
out['values'] = res
|
||||
|
||||
# return num of entries
|
||||
qm = frappe.form_dict.get('query_max') or ''
|
||||
if qm and qm.strip():
|
||||
if qm.split()[0].lower() != 'select':
|
||||
raise Exception('Query (Max) must be a SELECT')
|
||||
if not frappe.form_dict.get('simple_query'):
|
||||
qm = add_match_conditions(qm, tl)
|
||||
|
||||
out['n_values'] = frappe.utils.cint(frappe.db.sql(qm)[0][0])
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def runquery_csv():
|
||||
global out
|
||||
|
||||
q = frappe.form_dict.get('query')
|
||||
|
||||
rep_name = frappe.form_dict.get('report_name')
|
||||
if not frappe.form_dict.get('simple_query'):
|
||||
|
||||
# Report Name
|
||||
if not rep_name:
|
||||
rep_name = get_sql_tables(q)[0]
|
||||
|
||||
if not rep_name: rep_name = 'DataExport'
|
||||
|
||||
rows = [[rep_name], out['colnames']] + out['values']
|
||||
|
||||
from six import StringIO
|
||||
import csv
|
||||
|
||||
f = StringIO()
|
||||
writer = csv.writer(f)
|
||||
for r in rows:
|
||||
# encode only unicode type strings and not int, floats etc.
|
||||
writer.writerow(map(lambda v: isinstance(v, text_type) and v.encode('utf-8') or v, r))
|
||||
|
||||
f.seek(0)
|
||||
out['result'] = text_type(f.read(), 'utf-8')
|
||||
out['type'] = 'csv'
|
||||
out['doctype'] = rep_name
|
||||
|
||||
def add_limit_to_query(query, args):
|
||||
"""
|
||||
Add limit condition to query
|
||||
can be used by methods called in listing to add limit condition
|
||||
"""
|
||||
if args.get('limit_page_length'):
|
||||
query += """
|
||||
limit %(limit_start)s, %(limit_page_length)s"""
|
||||
|
||||
import frappe.utils
|
||||
args['limit_start'] = frappe.utils.cint(args.get('limit_start'))
|
||||
args['limit_page_length'] = frappe.utils.cint(args.get('limit_page_length'))
|
||||
|
||||
return query, args
|
||||
|
|
@ -8,14 +8,13 @@ import os, json
|
|||
|
||||
from frappe import _
|
||||
from frappe.modules import scrub, get_module_path
|
||||
from frappe.utils import flt, cint, get_html_format, get_url_to_form
|
||||
from frappe.utils import flt, cint, get_html_format, get_url_to_form, gzip_decompress, format_duration
|
||||
from frappe.model.utils import render_include
|
||||
from frappe.translate import send_translations
|
||||
import frappe.desk.reportview
|
||||
from frappe.permissions import get_role_permissions
|
||||
from six import string_types, iteritems
|
||||
from datetime import timedelta
|
||||
from frappe.utils import gzip_decompress
|
||||
from frappe.core.utils import ljust_list
|
||||
|
||||
def get_report_doc(report_name):
|
||||
|
|
@ -67,7 +66,7 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None)
|
|||
# Reordered columns
|
||||
columns = json.loads(report.custom_columns)
|
||||
|
||||
result = reorder_data_for_custom_columns(columns, query_columns, result, report.report_type)
|
||||
result = reorder_data_for_custom_columns(columns, query_columns, result)
|
||||
|
||||
result = add_data_to_custom_columns(columns, result)
|
||||
|
||||
|
|
@ -168,7 +167,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False, cust
|
|||
|
||||
result = None
|
||||
|
||||
if report.prepared_report and not report.disable_prepared_report and not ignore_prepared_report:
|
||||
if report.prepared_report and not report.disable_prepared_report and not ignore_prepared_report and not custom_columns:
|
||||
if filters:
|
||||
if isinstance(filters, string_types):
|
||||
filters = json.loads(filters)
|
||||
|
|
@ -215,25 +214,19 @@ def add_data_to_custom_columns(columns, result):
|
|||
|
||||
return data
|
||||
|
||||
def reorder_data_for_custom_columns(custom_columns, columns, result, report_type):
|
||||
def reorder_data_for_custom_columns(custom_columns, columns, result):
|
||||
if not result:
|
||||
return []
|
||||
|
||||
if report_type == 'Query Report':
|
||||
# Assume list result for query reports
|
||||
# Query report columns exclusively use Label
|
||||
custom_column_labels = [col["label"] for col in custom_columns]
|
||||
original_column_labels = [col.split(":")[0] for col in columns]
|
||||
return get_columns_from_list(custom_column_labels, original_column_labels, result)
|
||||
|
||||
custom_column_names = [col["fieldname"] for col in custom_columns]
|
||||
columns = [get_column_as_dict(col) for col in columns]
|
||||
if isinstance(result[0], list) or isinstance(result[0], tuple):
|
||||
# If the result is a list of lists
|
||||
original_column_names = [col["fieldname"] for col in columns]
|
||||
custom_column_names = [col["label"] for col in custom_columns]
|
||||
original_column_names = [col["label"] for col in columns]
|
||||
return get_columns_from_list(custom_column_names, original_column_names, result)
|
||||
else:
|
||||
# If the result is a list of dicts
|
||||
return get_columns_from_dict(custom_column_names, result)
|
||||
# columns do not need to be reordered if result is a list of dicts
|
||||
return result
|
||||
|
||||
def get_columns_from_list(columns, target_columns, result):
|
||||
reordered_result = []
|
||||
|
|
@ -251,21 +244,6 @@ def get_columns_from_list(columns, target_columns, result):
|
|||
|
||||
return reordered_result
|
||||
|
||||
def get_columns_from_dict(columns, result):
|
||||
reordered_result = []
|
||||
|
||||
for res in result:
|
||||
r = {}
|
||||
for col_name in columns:
|
||||
try:
|
||||
r[col_name] = res[col_name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
reordered_result.append(r)
|
||||
|
||||
return reordered_result
|
||||
|
||||
def get_prepared_report_result(report, filters, dn="", user=None):
|
||||
latest_report_data = {}
|
||||
doc = None
|
||||
|
|
@ -360,6 +338,7 @@ def export_query():
|
|||
columns = get_columns_dict(data.columns)
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
data['result'] = handle_duration_fieldtype_values(data.get('result'), data.get('columns'))
|
||||
xlsx_data = build_xlsx_data(columns, data, visible_idx, include_indentation)
|
||||
xlsx_file = make_xlsx(xlsx_data, "Query Report")
|
||||
|
||||
|
|
@ -367,6 +346,29 @@ def export_query():
|
|||
frappe.response['filecontent'] = xlsx_file.getvalue()
|
||||
frappe.response['type'] = 'binary'
|
||||
|
||||
def handle_duration_fieldtype_values(result, columns):
|
||||
for i, col in enumerate(columns):
|
||||
fieldtype = None
|
||||
if isinstance(col, string_types):
|
||||
col = col.split(":")
|
||||
if len(col) > 1:
|
||||
if col[1]:
|
||||
fieldtype = col[1]
|
||||
if "/" in fieldtype:
|
||||
fieldtype, options = fieldtype.split("/")
|
||||
else:
|
||||
fieldtype = "Data"
|
||||
else:
|
||||
fieldtype = col.get("fieldtype")
|
||||
|
||||
if fieldtype == "Duration":
|
||||
for entry in range(0, len(result)):
|
||||
val_in_seconds = result[entry][i]
|
||||
if val_in_seconds:
|
||||
duration_val = format_duration(val_in_seconds)
|
||||
result[entry][i] = duration_val
|
||||
|
||||
return result
|
||||
|
||||
def build_xlsx_data(columns, data, visible_idx, include_indentation):
|
||||
result = [[]]
|
||||
|
|
@ -384,12 +386,14 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation):
|
|||
|
||||
if isinstance(row, dict) and row:
|
||||
for idx in range(len(data.columns)):
|
||||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
if cint(include_indentation) and 'indent' in row and idx == 0:
|
||||
cell_value = (' ' * cint(row['indent'])) + cell_value
|
||||
row_data.append(cell_value)
|
||||
# check if column is not hidden
|
||||
if not columns[idx].get("hidden"):
|
||||
label = columns[idx]["label"]
|
||||
fieldname = columns[idx]["fieldname"]
|
||||
cell_value = row.get(fieldname, row.get(label, ""))
|
||||
if cint(include_indentation) and 'indent' in row and idx == 0:
|
||||
cell_value = (' ' * cint(row['indent'])) + cell_value
|
||||
row_data.append(cell_value)
|
||||
else:
|
||||
row_data = row
|
||||
|
||||
|
|
@ -427,7 +431,7 @@ def add_total_row(result, columns, meta = None):
|
|||
if i >= len(row): continue
|
||||
|
||||
cell = row.get(fieldname) if isinstance(row, dict) else row[i]
|
||||
if fieldtype in ["Currency", "Int", "Float", "Percent"] and flt(cell):
|
||||
if fieldtype in ["Currency", "Int", "Float", "Percent", "Duration"] and flt(cell):
|
||||
total_row[i] = flt(total_row[i]) + flt(cell)
|
||||
|
||||
if fieldtype == "Percent" and i not in has_percent:
|
||||
|
|
@ -462,6 +466,9 @@ def add_total_row(result, columns, meta = None):
|
|||
@frappe.whitelist()
|
||||
def get_data_for_custom_field(doctype, field):
|
||||
|
||||
if not frappe.has_permission(doctype, "read"):
|
||||
frappe.throw(_("Not Permitted"), frappe.PermissionError)
|
||||
|
||||
value_map = frappe._dict(frappe.get_all(doctype,
|
||||
fields=["name", field],
|
||||
as_list=1))
|
||||
|
|
@ -635,31 +642,35 @@ def get_columns_dict(columns):
|
|||
"""
|
||||
columns_dict = frappe._dict()
|
||||
for idx, col in enumerate(columns):
|
||||
col_dict = frappe._dict()
|
||||
|
||||
# string
|
||||
if isinstance(col, string_types):
|
||||
col = col.split(":")
|
||||
if len(col) > 1:
|
||||
if "/" in col[1]:
|
||||
col_dict["fieldtype"], col_dict["options"] = col[1].split("/")
|
||||
else:
|
||||
col_dict["fieldtype"] = col[1]
|
||||
|
||||
col_dict["label"] = col[0]
|
||||
col_dict["fieldname"] = frappe.scrub(col[0])
|
||||
|
||||
# dict
|
||||
else:
|
||||
col_dict.update(col)
|
||||
if "fieldname" not in col_dict:
|
||||
col_dict["fieldname"] = frappe.scrub(col_dict["label"])
|
||||
|
||||
col_dict = get_column_as_dict(col)
|
||||
columns_dict[idx] = col_dict
|
||||
columns_dict[col_dict["fieldname"]] = col_dict
|
||||
|
||||
return columns_dict
|
||||
|
||||
def get_column_as_dict(col):
|
||||
col_dict = frappe._dict()
|
||||
|
||||
# string
|
||||
if isinstance(col, string_types):
|
||||
col = col.split(":")
|
||||
if len(col) > 1:
|
||||
if "/" in col[1]:
|
||||
col_dict["fieldtype"], col_dict["options"] = col[1].split("/")
|
||||
else:
|
||||
col_dict["fieldtype"] = col[1]
|
||||
|
||||
col_dict["label"] = col[0]
|
||||
col_dict["fieldname"] = frappe.scrub(col[0])
|
||||
|
||||
# dict
|
||||
else:
|
||||
col_dict.update(col)
|
||||
if "fieldname" not in col_dict:
|
||||
col_dict["fieldname"] = frappe.scrub(col_dict["label"])
|
||||
|
||||
return col_dict
|
||||
|
||||
def get_user_match_filters(doctypes, user):
|
||||
match_filters = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from frappe.model.db_query import DatabaseQuery
|
|||
from frappe import _
|
||||
from six import string_types, StringIO
|
||||
from frappe.core.doctype.access_log.access_log import make_access_log
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, format_duration
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
@ -36,6 +36,7 @@ def get_form_params():
|
|||
data.pop('data', None)
|
||||
data.pop('ignore_permissions', None)
|
||||
data.pop('view', None)
|
||||
data.pop('user', None)
|
||||
|
||||
if "csrf_token" in data:
|
||||
del data["csrf_token"]
|
||||
|
|
@ -166,6 +167,8 @@ def export_query():
|
|||
for i, row in enumerate(ret):
|
||||
data.append([i+1] + list(row))
|
||||
|
||||
data = handle_duration_fieldtype_values(doctype, data, db_query.fields)
|
||||
|
||||
if file_format_type == "CSV":
|
||||
|
||||
# convert to csv
|
||||
|
|
@ -235,6 +238,29 @@ def get_labels(fields, doctype):
|
|||
|
||||
return labels
|
||||
|
||||
def handle_duration_fieldtype_values(doctype, data, fields):
|
||||
for field in fields:
|
||||
key = field.split(" as ")[0]
|
||||
|
||||
if key.startswith(('count(', 'sum(', 'avg(')): continue
|
||||
|
||||
if "." in key:
|
||||
parenttype, fieldname = key.split(".")[0][4:-1], key.split(".")[1].strip("`")
|
||||
else:
|
||||
parenttype = doctype
|
||||
fieldname = field.strip("`")
|
||||
|
||||
df = frappe.get_meta(parenttype).get_field(fieldname)
|
||||
|
||||
if df and df.fieldtype == 'Duration':
|
||||
index = fields.index(field) + 1
|
||||
for i in range(1, len(data)):
|
||||
val_in_seconds = data[i][index]
|
||||
if val_in_seconds:
|
||||
duration_val = format_duration(val_in_seconds, df.hide_days)
|
||||
data[i][index] = duration_val
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
def delete_items():
|
||||
"""delete selected items"""
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@ import frappe
|
|||
from frappe import _
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_all_nodes(doctype, parent, tree_method, **filters):
|
||||
def get_all_nodes(doctype, label, parent, tree_method, **filters):
|
||||
'''Recursively gets all data from tree nodes'''
|
||||
|
||||
if 'cmd' in filters:
|
||||
del filters['cmd']
|
||||
|
||||
filters.pop('data', None)
|
||||
|
||||
tree_method = frappe.get_attr(tree_method)
|
||||
|
|
@ -20,7 +19,7 @@ def get_all_nodes(doctype, parent, tree_method, **filters):
|
|||
frappe.throw(_("Not Permitted"), frappe.PermissionError)
|
||||
|
||||
data = tree_method(doctype, parent, **filters)
|
||||
out = [dict(parent=parent, data=data)]
|
||||
out = [dict(parent=label, data=data)]
|
||||
|
||||
if 'is_root' in filters:
|
||||
del filters['is_root']
|
||||
|
|
|
|||
|
|
@ -3,23 +3,7 @@
|
|||
|
||||
frappe.ui.form.on('Auto Email Report', {
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.report_type !== 'Report Builder') {
|
||||
if(frm.script_setup_for !== frm.doc.report && !frm.doc.__islocal) {
|
||||
frappe.call({
|
||||
method:"frappe.desk.query_report.get_script",
|
||||
args: {
|
||||
report_name: frm.doc.report
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.dom.eval(r.message.script || "");
|
||||
frm.script_setup_for = frm.doc.report;
|
||||
frm.trigger('show_filters');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.trigger('show_filters');
|
||||
}
|
||||
}
|
||||
frm.trigger('fetch_report_filters');
|
||||
if(!frm.is_new()) {
|
||||
frm.add_custom_button(__('Download'), function() {
|
||||
var w = window.open(
|
||||
|
|
@ -50,6 +34,27 @@ frappe.ui.form.on('Auto Email Report', {
|
|||
},
|
||||
report: function(frm) {
|
||||
frm.set_value('filters', '');
|
||||
frm.trigger('fetch_report_filters');
|
||||
},
|
||||
fetch_report_filters(frm) {
|
||||
if (frm.doc.report
|
||||
&& frm.doc.report_type !== 'Report Builder'
|
||||
&& frm.script_setup_for !== frm.doc.report
|
||||
) {
|
||||
frappe.call({
|
||||
method: "frappe.desk.query_report.get_script",
|
||||
args: {
|
||||
report_name: frm.doc.report
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.dom.eval(r.message.script || "");
|
||||
frm.script_setup_for = frm.doc.report;
|
||||
frm.trigger('show_filters');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.trigger('show_filters');
|
||||
}
|
||||
},
|
||||
show_filters: function(frm) {
|
||||
var wrapper = $(frm.get_field('filters_display').wrapper);
|
||||
|
|
|
|||
|
|
@ -1,181 +1,78 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2019-01-09 16:39:23.746535",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2019-01-09 16:39:23.746535",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"ref_doctype",
|
||||
"ref_docname",
|
||||
"user"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "ref_doctype",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Doctype",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "ref_doctype",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Doctype",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "ref_docname",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Document Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "ref_doctype",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "ref_docname",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Document Name",
|
||||
"options": "ref_doctype",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-26 15:43:44.330348",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Document Follow",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-17 09:19:28.496453",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Document Follow",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -4,12 +4,13 @@
|
|||
frappe.ui.form.on("Email Queue", {
|
||||
refresh: function(frm) {
|
||||
if (["Not Sent","Partially Sent"].indexOf(frm.doc.status)!=-1) {
|
||||
frm.add_custom_button("Send Now", function() {
|
||||
let button = frm.add_custom_button("Send Now", function() {
|
||||
frappe.call({
|
||||
method: 'frappe.email.doctype.email_queue.email_queue.send_now',
|
||||
args: {
|
||||
name: frm.doc.name
|
||||
},
|
||||
btn: button,
|
||||
callback: function() {
|
||||
frm.reload_doc();
|
||||
}
|
||||
|
|
@ -18,12 +19,13 @@ frappe.ui.form.on("Email Queue", {
|
|||
}
|
||||
|
||||
if (["Error","Partially Errored"].indexOf(frm.doc.status)!=-1) {
|
||||
frm.add_custom_button("Retry Sending", function() {
|
||||
let button = frm.add_custom_button("Retry Sending", function() {
|
||||
frm.call({
|
||||
method: "retry_sending",
|
||||
args: {
|
||||
name: frm.doc.name
|
||||
},
|
||||
btn: button,
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
frm.set_value("status", "Not Sent");
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ frappe.ui.form.on('Newsletter', {
|
|||
});
|
||||
}, "fa fa-play", "btn-success");
|
||||
}
|
||||
if (!doc.__islocal && cint(doc.email_sent)) {
|
||||
frm.set_df_property('schedule_send', "read_only", 1);
|
||||
}
|
||||
|
||||
frm.events.setup_dashboard(frm);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@
|
|||
"email_sent",
|
||||
"newsletter_content",
|
||||
"subject",
|
||||
"content_type",
|
||||
"message",
|
||||
"message_md",
|
||||
"message_html",
|
||||
"send_unsubscribe_link",
|
||||
"send_attachments",
|
||||
"published",
|
||||
|
|
@ -37,8 +40,7 @@
|
|||
"fieldname": "send_from",
|
||||
"fieldtype": "Data",
|
||||
"ignore_xss_filter": 1,
|
||||
"label": "Sender",
|
||||
"no_copy": 1
|
||||
"label": "Sender"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -50,7 +52,8 @@
|
|||
},
|
||||
{
|
||||
"fieldname": "newsletter_content",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Content"
|
||||
},
|
||||
{
|
||||
"fieldname": "subject",
|
||||
|
|
@ -61,11 +64,12 @@
|
|||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.content_type === 'Rich Text'",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Message",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval: doc.content_type === 'Rich Text'"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
|
|
@ -87,16 +91,20 @@
|
|||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "test_the_newsletter",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Testing"
|
||||
},
|
||||
{
|
||||
"description": "A Lead with this Email Address should exist",
|
||||
"fieldname": "test_email_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Test Email Address"
|
||||
"label": "Test Email Address",
|
||||
"options": "Email"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.test_email_id",
|
||||
"fieldname": "test_send",
|
||||
"fieldtype": "Button",
|
||||
"label": "Test",
|
||||
|
|
@ -117,7 +125,8 @@
|
|||
"depends_on": "eval: doc.schedule_sending",
|
||||
"fieldname": "schedule_send",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Schedule Send"
|
||||
"label": "Schedule Send",
|
||||
"read_only_depends_on": "eval: doc.email_sent"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
|
@ -125,11 +134,32 @@
|
|||
"fieldtype": "Check",
|
||||
"label": "Send Attachments"
|
||||
},
|
||||
{
|
||||
"fieldname": "content_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Type",
|
||||
"options": "Rich Text\nMarkdown\nHTML"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.content_type === 'Markdown'",
|
||||
"fieldname": "message_md",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Message (Markdown)",
|
||||
"mandatory_depends_on": "eval:doc.content_type === 'Markdown'"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.content_type === 'HTML'",
|
||||
"fieldname": "message_html",
|
||||
"fieldtype": "HTML Editor",
|
||||
"label": "Message (HTML)",
|
||||
"mandatory_depends_on": "eval:doc.content_type === 'HTML'"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "schedule_sending",
|
||||
"fieldtype": "Check",
|
||||
"label": "Schedule Sending"
|
||||
"label": "Schedule Sending",
|
||||
"read_only_depends_on": "eval: doc.email_sent"
|
||||
}
|
||||
],
|
||||
"has_web_view": 1,
|
||||
|
|
@ -139,7 +169,7 @@
|
|||
"is_published_field": "published",
|
||||
"links": [],
|
||||
"max_attachments": 3,
|
||||
"modified": "2020-08-17 18:11:59.541686",
|
||||
"modified": "2020-08-24 19:59:37.262500",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Newsletter",
|
||||
|
|
|
|||
|
|
@ -8,12 +8,9 @@ import frappe.utils
|
|||
from frappe import throw, _
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from frappe.utils.verified_command import get_signed_params, verify_request
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.email.queue import send
|
||||
from frappe.email.doctype.email_group.email_group import add_subscribers
|
||||
from frappe.utils import parse_addr, now_datetime
|
||||
from frappe.utils import validate_email_address
|
||||
|
||||
from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address
|
||||
|
||||
class Newsletter(WebsiteGenerator):
|
||||
def onload(self):
|
||||
|
|
@ -29,8 +26,8 @@ class Newsletter(WebsiteGenerator):
|
|||
|
||||
def test_send(self, doctype="Lead"):
|
||||
self.recipients = frappe.utils.split_emails(self.test_email_id)
|
||||
self.queue_all()
|
||||
frappe.msgprint(_("Scheduled to send to {0}").format(self.test_email_id))
|
||||
self.queue_all(test_email=True)
|
||||
frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id))
|
||||
|
||||
def send_emails(self):
|
||||
"""send emails to leads and customers"""
|
||||
|
|
@ -40,21 +37,13 @@ class Newsletter(WebsiteGenerator):
|
|||
self.recipients = self.get_recipients()
|
||||
|
||||
if self.recipients:
|
||||
if getattr(frappe.local, "is_ajax", False):
|
||||
self.validate_send()
|
||||
# using default queue with a longer timeout as this isn't a scheduled task
|
||||
enqueue(send_newsletter, queue='default', timeout=6000, event='send_newsletter',
|
||||
newsletter=self.name)
|
||||
|
||||
else:
|
||||
self.queue_all()
|
||||
|
||||
frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients)))
|
||||
self.queue_all()
|
||||
frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients)))
|
||||
|
||||
else:
|
||||
frappe.msgprint(_("Newsletter should have atleast one recipient"))
|
||||
|
||||
def queue_all(self):
|
||||
def queue_all(self, test_email=False):
|
||||
if not self.get("recipients"):
|
||||
# in case it is called via worker
|
||||
self.recipients = self.get_recipients()
|
||||
|
|
@ -80,7 +69,7 @@ class Newsletter(WebsiteGenerator):
|
|||
frappe.throw(_("Unable to find attachment {0}").format(file.name))
|
||||
|
||||
send(recipients=self.recipients, sender=sender,
|
||||
subject=self.subject, message=self.message,
|
||||
subject=self.subject, message=self.get_message(),
|
||||
reference_doctype=self.doctype, reference_name=self.name,
|
||||
add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments,
|
||||
unsubscribe_method="/unsubscribe",
|
||||
|
|
@ -90,9 +79,18 @@ class Newsletter(WebsiteGenerator):
|
|||
if not frappe.flags.in_test:
|
||||
frappe.db.auto_commit_on_many_writes = False
|
||||
|
||||
self.db_set("email_sent", 1)
|
||||
self.db_set("schedule_send", now_datetime())
|
||||
self.db_set("scheduled_to_send", len(self.recipients))
|
||||
if not test_email:
|
||||
self.db_set("email_sent", 1)
|
||||
self.db_set("schedule_send", now_datetime())
|
||||
self.db_set("scheduled_to_send", len(self.recipients))
|
||||
|
||||
def get_message(self):
|
||||
|
||||
return {
|
||||
'Rich Text': self.message,
|
||||
'Markdown': markdown(self.message_md),
|
||||
'HTML': self.message_html
|
||||
}[self.content_type or 'Rich Text']
|
||||
|
||||
def get_recipients(self):
|
||||
"""Get recipients from Email Group"""
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
frappe.listview_settings['Newsletter'] = {
|
||||
add_fields: ["subject", "email_sent"],
|
||||
add_fields: ["subject", "email_sent", "schedule_sending"],
|
||||
get_indicator: function(doc) {
|
||||
if(doc.email_sent) {
|
||||
if (doc.email_sent) {
|
||||
return [__("Sent"), "green", "email_sent,=,Yes"];
|
||||
} else if (doc.schedule_sending) {
|
||||
return [__("Scheduled"), "orange", "email_sent,=,No|schedule_sending,=,Yes"];
|
||||
} else {
|
||||
return [__("Not Sent"), "orange", "email_sent,=,No"];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ class TestNewsletter(unittest.TestCase):
|
|||
"doctype": "Newsletter",
|
||||
"subject": "_Test Newsletter",
|
||||
"send_from": "Test Sender <test_sender@example.com>",
|
||||
"content_type": "Rich Text",
|
||||
"message": "Testing my news.",
|
||||
"published": published,
|
||||
"schedule_sending": bool(schedule_send),
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ frappe.notification = {
|
|||
}
|
||||
|
||||
frappe.model.with_doctype(frm.doc.document_type, function() {
|
||||
let get_select_options = function(df) {
|
||||
let get_select_options = function(df, parent_field) {
|
||||
// Append parent_field name along with fieldname for child table fields
|
||||
let select_value = parent_field ? df.fieldname + ',' + parent_field : df.fieldname;
|
||||
|
||||
return {
|
||||
value: df.fieldname,
|
||||
value: select_value,
|
||||
label: df.fieldname + ' (' + __(df.label) + ')'
|
||||
};
|
||||
};
|
||||
|
|
@ -59,9 +62,21 @@ frappe.notification = {
|
|||
let receiver_fields = [];
|
||||
if (frm.doc.channel === 'Email') {
|
||||
receiver_fields = $.map(fields, function(d) {
|
||||
return d.options == 'Email' ||
|
||||
(d.options == 'User' && d.fieldtype == 'Link')
|
||||
? get_select_options(d) : null;
|
||||
|
||||
// Add User and Email fields from child into select dropdown
|
||||
if (d.fieldtype == 'Table') {
|
||||
let child_fields = frappe.get_doc('DocType', d.options).fields;
|
||||
return $.map(child_fields, function(df) {
|
||||
return df.options == 'Email' ||
|
||||
(df.options == 'User' && df.fieldtype == 'Link')
|
||||
? get_select_options(df, d.fieldname) : null;
|
||||
});
|
||||
// Add User and Email fields from parent into select dropdown
|
||||
} else {
|
||||
return d.options == 'Email' ||
|
||||
(d.options == 'User' && d.fieldtype == 'Link')
|
||||
? get_select_options(d) : null;
|
||||
}
|
||||
});
|
||||
} else if (in_list(['WhatsApp', 'SMS'], frm.doc.channel)) {
|
||||
receiver_fields = $.map(fields, function(d) {
|
||||
|
|
@ -87,7 +102,7 @@ frappe.notification = {
|
|||
<h5>Message Example</h5>
|
||||
|
||||
<pre>
|
||||
Your {{ doc.name }} order of {{ doc.total }} has shipped and should be delivered on {{ doc.date }}. Details : {{doc.customer}}
|
||||
Your appointment is coming up on {{ doc.date }} at {{ doc.time }}
|
||||
</pre>`;
|
||||
} else if (frm.doc.channel === 'Email') {
|
||||
template = `<h5>Message Example</h5>
|
||||
|
|
@ -151,6 +166,7 @@ frappe.ui.form.on('Notification', {
|
|||
},
|
||||
refresh: function(frm) {
|
||||
frappe.notification.setup_fieldname_select(frm);
|
||||
frappe.notification.setup_example_message(frm);
|
||||
frm.get_field('is_standard').toggle(frappe.boot.developer_mode);
|
||||
frm.trigger('event');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"set_property_after_alert",
|
||||
"property_value",
|
||||
"column_break_5",
|
||||
"send_to_all_assignees",
|
||||
"recipients",
|
||||
"message_sb",
|
||||
"message",
|
||||
|
|
@ -66,7 +67,7 @@
|
|||
},
|
||||
{
|
||||
"depends_on": "eval:doc.channel=='Slack'",
|
||||
"description": "To use Slack Channel, add a <a href=\"\\#Form/Slack Webhook URL\">Slack Webhook URL</a>.",
|
||||
"description": "To use Slack Channel, add a <a href=\"#List/Slack%20Webhook%20URL/List\">Slack Webhook URL</a>.",
|
||||
"fieldname": "slack_webhook_url",
|
||||
"fieldtype": "Link",
|
||||
"label": "Slack Channel",
|
||||
|
|
@ -216,7 +217,7 @@
|
|||
"fieldname": "recipients",
|
||||
"fieldtype": "Table",
|
||||
"label": "Recipients",
|
||||
"mandatory_depends_on": "eval:doc.channel!=='Slack'",
|
||||
"mandatory_depends_on": "eval:doc.channel!=='Slack' && !doc.send_to_all_assignees",
|
||||
"options": "Notification Recipient"
|
||||
},
|
||||
{
|
||||
|
|
@ -268,6 +269,7 @@
|
|||
"fieldname": "twilio_number",
|
||||
"fieldtype": "Link",
|
||||
"label": "Twilio Number",
|
||||
"mandatory_depends_on": "eval: doc.channel==='WhatsApp'",
|
||||
"options": "Twilio Number Group"
|
||||
},
|
||||
{
|
||||
|
|
@ -277,11 +279,19 @@
|
|||
"fieldname": "send_system_notification",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send System Notification"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.channel == 'Email'",
|
||||
"fieldname": "send_to_all_assignees",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send To All Assignees"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-envelope",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-11 19:24:35.479373",
|
||||
"modified": "2020-09-03 10:33:23.084590",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Notification",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from frappe.model.document import Document
|
|||
from frappe.core.doctype.role.role import get_info_based_on_role, get_user_info
|
||||
from frappe.utils import validate_email_address, nowdate, parse_val, is_html, add_to_date
|
||||
from frappe.utils.jinja import validate_template
|
||||
from frappe.utils.safe_exec import get_safe_globals
|
||||
from frappe.modules.utils import export_module_json, get_doc_module
|
||||
from six import string_types
|
||||
from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message
|
||||
|
|
@ -42,6 +43,7 @@ class Notification(Document):
|
|||
self.validate_forbidden_types()
|
||||
self.validate_condition()
|
||||
self.validate_standard()
|
||||
self.validate_twilio_settings()
|
||||
frappe.cache().hdel('notifications', self.document_type)
|
||||
|
||||
def on_update(self):
|
||||
|
|
@ -68,6 +70,11 @@ def get_context(context):
|
|||
if self.is_standard and not frappe.conf.developer_mode:
|
||||
frappe.throw(_('Cannot edit Standard Notification. To edit, please disable this and duplicate it'))
|
||||
|
||||
def validate_twilio_settings(self):
|
||||
if self.enabled and self.channel == "WhatsApp" \
|
||||
and not frappe.db.get_single_value("Twilio Settings", "enabled"):
|
||||
frappe.throw(_("Please enable Twilio settings to send WhatsApp messages"))
|
||||
|
||||
def validate_condition(self):
|
||||
temp_doc = frappe.new_doc(self.document_type)
|
||||
if self.condition:
|
||||
|
|
@ -166,8 +173,13 @@ def get_context(context):
|
|||
subject = frappe.render_template(self.subject, context)
|
||||
|
||||
attachments = self.get_attachment(doc)
|
||||
|
||||
recipients, cc, bcc = self.get_list_of_recipients(doc, context)
|
||||
|
||||
users = recipients + cc + bcc
|
||||
|
||||
if not users:
|
||||
return
|
||||
|
||||
notification_doc = {
|
||||
'type': 'Alert',
|
||||
|
|
@ -189,6 +201,7 @@ def get_context(context):
|
|||
recipients, cc, bcc = self.get_list_of_recipients(doc, context)
|
||||
if not (recipients or cc or bcc):
|
||||
return
|
||||
|
||||
sender = None
|
||||
if self.sender and self.sender_email:
|
||||
sender = formataddr((self.sender, self.sender_email))
|
||||
|
|
@ -234,13 +247,20 @@ def get_context(context):
|
|||
if not frappe.safe_eval(recipient.condition, None, context):
|
||||
continue
|
||||
if recipient.receiver_by_document_field:
|
||||
email_ids_value = doc.get(recipient.receiver_by_document_field)
|
||||
if validate_email_address(email_ids_value):
|
||||
email_ids = email_ids_value.replace(",", "\n")
|
||||
recipients = recipients + email_ids.split("\n")
|
||||
fields = recipient.receiver_by_document_field.split(',')
|
||||
# fields from child table
|
||||
if len(fields) > 1:
|
||||
for d in doc.get(fields[1]):
|
||||
email_id = d.get(fields[0])
|
||||
if validate_email_address(email_id):
|
||||
recipients.append(email_id)
|
||||
# field from parent doc
|
||||
else:
|
||||
email_ids_value = doc.get(fields[0])
|
||||
if validate_email_address(email_ids_value):
|
||||
email_ids = email_ids_value.replace(",", "\n")
|
||||
recipients = recipients + email_ids.split("\n")
|
||||
|
||||
# else:
|
||||
# print "invalid email"
|
||||
if recipient.cc and "{" in recipient.cc:
|
||||
recipient.cc = frappe.render_template(recipient.cc, context)
|
||||
|
||||
|
|
@ -262,8 +282,9 @@ def get_context(context):
|
|||
for email in emails:
|
||||
recipients = recipients + email.split("\n")
|
||||
|
||||
if not recipients and not cc and not bcc:
|
||||
return None, None, None
|
||||
if self.send_to_all_assignees:
|
||||
recipients = recipients + get_assignees(doc)
|
||||
|
||||
return list(set(recipients)), list(set(cc)), list(set(bcc))
|
||||
|
||||
def get_receiver_list(self, doc, context):
|
||||
|
|
@ -404,4 +425,13 @@ def evaluate_alert(doc, alert, event):
|
|||
frappe.utils.get_link_to_form('Error Log', error_log.name)))
|
||||
|
||||
def get_context(doc):
|
||||
return {"doc": doc, "nowdate": nowdate, "frappe": frappe._dict(utils=frappe.utils)}
|
||||
return {"doc": doc, "nowdate": nowdate, "frappe": frappe._dict(utils=get_safe_globals().get("frappe").get("utils"))}
|
||||
|
||||
def get_assignees(doc):
|
||||
assignees = []
|
||||
assignees = frappe.get_all('ToDo', filters={'status': 'Open', 'reference_name': doc.name,
|
||||
'reference_type': doc.doctype}, fields=['owner'])
|
||||
|
||||
recipients = [d.owner for d in assignees]
|
||||
|
||||
return recipients
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import frappe, frappe.utils, frappe.utils.scheduler
|
||||
from frappe.desk.form import assign_to
|
||||
import unittest
|
||||
|
||||
test_records = frappe.get_test_records('Notification')
|
||||
|
|
@ -13,7 +14,31 @@ test_dependencies = ["User"]
|
|||
class TestNotification(unittest.TestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("""delete from `tabEmail Queue`""")
|
||||
frappe.set_user("test1@example.com")
|
||||
frappe.set_user("test@example.com")
|
||||
|
||||
if not frappe.db.exists('Notification', {'name': 'ToDo Status Update'}, 'name'):
|
||||
notification = frappe.new_doc('Notification')
|
||||
notification.name = 'ToDo Status Update'
|
||||
notification.subject = 'ToDo Status Update'
|
||||
notification.document_type = 'ToDo'
|
||||
notification.event = 'Value Change'
|
||||
notification.value_changed = 'status'
|
||||
notification.send_to_all_assignees = 1
|
||||
notification.save()
|
||||
|
||||
if not frappe.db.exists('Notification', {'name': 'Contact Status Update'}, 'name'):
|
||||
notification = frappe.new_doc('Notification')
|
||||
notification.name = 'Contact Status Update'
|
||||
notification.subject = 'Contact Status Update'
|
||||
notification.document_type = 'Contact'
|
||||
notification.event = 'Value Change'
|
||||
notification.value_changed = 'status'
|
||||
notification.message = 'Test Contact Update'
|
||||
notification.append('recipients', {
|
||||
'receiver_by_document_field': 'email_id,email_ids'
|
||||
})
|
||||
notification.save()
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
|
|
@ -177,3 +202,65 @@ class TestNotification(unittest.TestCase):
|
|||
frappe.db.sql("""delete from `tabUser` where email='test_jinja@example.com'""")
|
||||
frappe.db.sql("""delete from `tabEmail Queue`""")
|
||||
frappe.db.sql("""delete from `tabEmail Queue Recipient`""")
|
||||
|
||||
def test_notification_to_assignee(self):
|
||||
todo = frappe.new_doc('ToDo')
|
||||
todo.description = 'Test Notification'
|
||||
todo.save()
|
||||
|
||||
assign_to.add({
|
||||
"assign_to": ["test2@example.com"],
|
||||
"doctype": todo.doctype,
|
||||
"name": todo.name,
|
||||
"description": "Close this Todo"
|
||||
})
|
||||
|
||||
assign_to.add({
|
||||
"assign_to": ["test1@example.com"],
|
||||
"doctype": todo.doctype,
|
||||
"name": todo.name,
|
||||
"description": "Close this Todo"
|
||||
})
|
||||
|
||||
#change status of todo
|
||||
todo.status = 'Closed'
|
||||
todo.save()
|
||||
|
||||
email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'ToDo',
|
||||
'reference_name': todo.name})
|
||||
|
||||
self.assertTrue(email_queue)
|
||||
|
||||
recipients = [d.recipient for d in email_queue.recipients]
|
||||
self.assertTrue('test2@example.com' in recipients)
|
||||
self.assertTrue('test1@example.com' in recipients)
|
||||
|
||||
def test_notification_by_child_table_field(self):
|
||||
contact = frappe.new_doc('Contact')
|
||||
contact.first_name = 'John Doe'
|
||||
contact.status = 'Open'
|
||||
contact.append('email_ids', {
|
||||
'email_id': 'test2@example.com',
|
||||
'is_primary': 1
|
||||
})
|
||||
|
||||
contact.append('email_ids', {
|
||||
'email_id': 'test1@example.com'
|
||||
})
|
||||
|
||||
contact.save()
|
||||
|
||||
#change status of contact
|
||||
contact.status = 'Replied'
|
||||
contact.save()
|
||||
|
||||
email_queue = frappe.get_doc('Email Queue', {'reference_doctype': 'Contact',
|
||||
'reference_name': contact.name})
|
||||
|
||||
self.assertTrue(email_queue)
|
||||
|
||||
recipients = [d.recipient for d in email_queue.recipients]
|
||||
self.assertTrue('test2@example.com' in recipients)
|
||||
self.assertTrue('test1@example.com' in recipients)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,10 @@
|
|||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-21 11:18:40.125233",
|
||||
"modified": "2020-09-01 17:40:27.289105",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Email",
|
||||
"name": "Notification Recipient",
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue