Commit ad1d8946 authored by Nabin Hait's avatar Nabin Hait
Browse files

Merge branch 'develop'

parents 8855f704 70ee7e8d
develop v11.1.5 v11.1.4 v11.1.3 v11.1.2 v11.1.1 v11.1.0 v11.0.3 v11.0.3-beta.51 v11.0.3-beta.50 v11.0.3-beta.49 v11.0.3-beta.48 v11.0.3-beta.47 v11.0.3-beta.46 v11.0.3-beta.45 v11.0.3-beta.44 v11.0.3-beta.43 v11.0.3-beta.42 v11.0.3-beta.41 v11.0.3-beta.40 v11.0.3-beta.39 v11.0.3-beta.38 v11.0.3-beta.37 v11.0.3-beta.36 v11.0.3-beta.35 v11.0.3-beta.34 v11.0.3-beta.33 v11.0.3-beta.32 v11.0.3-beta.31 v11.0.3-beta.30 v11.0.3-beta.29 v11.0.3-beta.28 v11.0.3-beta.27 v11.0.3-beta.26 v11.0.3-beta.25 v11.0.3-beta.24 v11.0.3-beta.23 v11.0.3-beta.22 v11.0.3-beta.21 v11.0.3-beta.20 v11.0.3-beta.19 v11.0.3-beta.18 v11.0.3-beta.17 v11.0.3-beta.16 v11.0.3-beta.15 v11.0.3-beta.14 v11.0.3-beta.13 v11.0.3-beta.12 v11.0.3-beta.11 v11.0.3-beta.10 v11.0.3-beta.9 v11.0.3-beta.8 v11.0.3-beta.7 v11.0.3-beta.6 v11.0.3-beta.5 v11.0.3-beta.4 v11.0.3-beta.3 v11.0.3-beta.2 v11.0.3-beta.1 v11.0.2 v11.0.1 v11.0.0-beta v10.1.71 v10.1.70 v10.1.69 v10.1.68 v10.1.67 v10.1.66 v10.1.65 v10.1.64 v10.1.63 v10.1.62 v10.1.61 v10.1.60 v10.1.59 v10.1.58 v10.1.57 v10.1.56 v10.1.55 v10.1.54 v10.1.53 v10.1.52 v10.1.51 v10.1.50 v10.1.49 v10.1.49-beta.1 v10.1.48 v10.1.47 v10.1.46 v10.1.45 v10.1.44 v10.1.43 v10.1.42 v10.1.41 v10.1.40 v10.1.39 v10.1.38 v10.1.37 v10.1.36 v10.1.35 v10.1.34 v10.1.33 v10.1.32 v10.1.31 v10.1.30 v10.1.29 v10.1.28 v10.1.27 v10.1.26 v10.1.25 v10.1.24 v10.1.23 v10.1.22 v10.1.21 v10.1.20 v10.1.19 v10.1.18 v10.1.17 v10.1.16 v10.1.15 v10.1.14 v10.1.13 v10.1.12 v10.1.11 v10.1.10 v10.1.9 v10.1.8 v10.1.7 v10.1.6 v10.1.5 v10.1.4 v10.1.3 v10.1.2 v10.1.1 v10.1.0 v10.0.25 v10.0.24 v10.0.23 v10.0.22 v10.0.21 v10.0.20 v10.0.19 v10.0.18 v10.0.17 v10.0.16 v10.0.15 v10.0.14 v10.0.13 v10.0.12 v10.0.11 v10.0.10 v10.0.9 v10.0.8 v10.0.7 v10.0.6 v10.0.5 v10.0.4 v10.0.3 v10.0.2 v10.0.1 v10.0.0
No related merge requests found
Showing with 183 additions and 87 deletions
+183 -87
......@@ -59,7 +59,6 @@
"PhotoSwipeUI_Default": true,
"fluxify": true,
"io": true,
"c3": true,
"__": true,
"_p": true,
"_f": true,
......@@ -119,6 +118,8 @@
"getCookies": true,
"get_url_arg": true,
"QUnit": true,
"JsBarcode": true
"JsBarcode": true,
"L": true,
"Chart": true
}
}
......@@ -10,4 +10,8 @@ dist/
build/
frappe/docs/current
.vscode
node_modules
\ No newline at end of file
node_modules
# Not Recommended, but will remove once webpack ready
package-lock.json
......@@ -30,7 +30,7 @@ install:
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
before_script:
- wget http://chromedriver.storage.googleapis.com/2.27/chromedriver_linux64.zip
- wget http://chromedriver.storage.googleapis.com/2.33/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- sudo apt-get install libnss3
- sudo apt-get --only-upgrade install google-chrome-stable
......
......@@ -40,7 +40,6 @@ Full-stack web application framework that uses Python and MariaDB on the server
### Website
For details and documentation, see the website
[https://frappe.io](https://frappe.io)
### License
......
......@@ -14,7 +14,7 @@ import os, sys, importlib, inspect, json
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template, get_email_from_template
__version__ = '9.2.25'
__version__ = '10.0.0'
__title__ = "Frappe Framework"
local = Local()
......@@ -311,6 +311,10 @@ def msgprint(msg, title=None, raise_exception=0, as_table=False, indicator=None,
def clear_messages():
local.message_log = []
def clear_last_message():
if len(local.message_log) > 0:
local.message_log = local.message_log[:-1]
def throw(msg, exc=ValidationError, title=None):
"""Throw execption and show message (`msgprint`).
......@@ -378,7 +382,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
as_markdown=False, delayed=True, reference_doctype=None, reference_name=None,
unsubscribe_method=None, unsubscribe_params=None, unsubscribe_message=None,
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
inline_images=None, template=None, args=None, header=None):
"""Send email using user's default **Email Account** or global default **Email Account**.
......@@ -426,7 +430,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
subject=subject, message=message, text_content=text_content,
reference_doctype = doctype or reference_doctype, reference_name = name or reference_name,
unsubscribe_method=unsubscribe_method, unsubscribe_params=unsubscribe_params, unsubscribe_message=unsubscribe_message,
attachments=attachments, reply_to=reply_to, cc=cc, message_id=message_id, in_reply_to=in_reply_to,
attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority,
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
inline_images=inline_images, header=header)
......@@ -972,9 +976,9 @@ def make_property_setter(args, ignore_validate=False, validate_fields_for_doctyp
ps.insert()
def import_doc(path, ignore_links=False, ignore_insert=False, insert=False):
"""Import a file using Data Import Tool."""
from frappe.core.page.data_import_tool import data_import_tool
data_import_tool.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
"""Import a file using Data Import."""
from frappe.core.doctype.data_import import data_import
data_import.import_doc(path, ignore_links=ignore_links, ignore_insert=ignore_insert, insert=insert)
def copy_doc(doc, ignore_no_copy=True):
""" No_copy fields also get copied."""
......@@ -1362,7 +1366,7 @@ def logger(module=None, with_more_info=True):
def log_error(message=None, title=None):
'''Log error to Error Log'''
get_doc(dict(doctype='Error Log', error=as_unicode(message or get_traceback()),
return get_doc(dict(doctype='Error Log', error=as_unicode(message or get_traceback()),
method=title)).insert(ignore_permissions=True)
def get_desk_link(doctype, name):
......
......@@ -4,7 +4,6 @@
from __future__ import unicode_literals
import os
import MySQLdb
from six import iteritems
import logging
......@@ -27,6 +26,12 @@ from frappe.utils.error import make_error_snapshot
from frappe.core.doctype.communication.comment import update_comments_in_parent_after_request
from frappe import _
# imports - third-party imports
import pymysql
from pymysql.constants import ER
# imports - module imports
local_manager = LocalManager([frappe.local])
_site = None
......@@ -116,8 +121,15 @@ def init_request(request):
frappe.local.http_request = frappe.auth.HTTPRequest()
def make_form_dict(request):
import json
if request.content_type == 'application/json':
args = json.loads(request.data)
else:
args = request.form or request.args
frappe.local.form_dict = frappe._dict({ k:v[0] if isinstance(v, (list, tuple)) else v \
for k, v in iteritems(request.form or request.args) })
for k, v in iteritems(args) })
if "_" in frappe.local.form_dict:
# _ is passed by $.ajax so that the request is not cached by the browser. So, remove _ from form_dict
......@@ -134,11 +146,8 @@ def handle_exception(e):
response = frappe.utils.response.report_error(http_status_code)
elif (http_status_code==500
and isinstance(e, MySQLdb.OperationalError)
and e.args[0] in (1205, 1213)):
# 1205 = lock wait timeout
# 1213 = deadlock
# code 409 represents conflict
and isinstance(e, pymysql.InternalError)
and e.args[0] in (ER.LOCK_WAIT_TIMEOUT, ER.LOCK_DEADLOCK)):
http_status_code = 508
elif http_status_code==401:
......
......@@ -15,7 +15,7 @@ from frappe.sessions import Session, clear_sessions, delete_session
from frappe.modules.patch_handler import check_session_stopped
from frappe.translate import get_lang_code
from frappe.utils.password import check_password
from frappe.core.doctype.authentication_log.authentication_log import add_authentication_log
from frappe.core.doctype.activity_log.activity_log import add_authentication_log
from frappe.utils.background_jobs import enqueue
from frappe.twofactor import (should_run_2fa, authenticate_for_2factor,
confirm_otp_token, get_cached_user_pass)
......
......@@ -20,6 +20,7 @@ const apps = apps_contents.split('\n');
const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app
const assets_path = path_join(sites_path, 'assets');
let build_map = make_build_map();
let compiled_js_cache = {}; // cache each js file after it is compiled
const file_watcher_port = get_conf().file_watcher_port;
// command line args
......@@ -65,11 +66,12 @@ function watch() {
io.emit('reload_css', filename);
}
});
watch_js(/*function (filename) {
if(socket_connection) {
io.emit('reload_js', filename);
}
}*/);
watch_js(//function (filename) {
// if(socket_connection) {
// io.emit('reload_js', filename);
// }
//}
);
watch_build_json();
});
......@@ -82,9 +84,7 @@ function watch() {
});
}
function pack(output_path, inputs, minify) {
const output_type = output_path.split('.').pop();
function pack(output_path, inputs, minify, file_changed) {
let output_txt = '';
for (const file of inputs) {
......@@ -93,25 +93,18 @@ function pack(output_path, inputs, minify) {
continue;
}
let file_content = fs.readFileSync(file, 'utf-8');
if (file.endsWith('.html') && output_type === 'js') {
file_content = html_to_js_template(file, file_content);
let force_compile = false;
if (file_changed) {
// if file_changed is passed and is equal to file, force_compile it
force_compile = file_changed === file;
}
if(file.endsWith('class.js')) {
file_content = minify_js(file_content, file);
}
if (file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
file_content = babelify(file_content, file, minify);
}
let file_content = get_compiled_file(file, output_path, minify, force_compile);
if(!minify) {
output_txt += `\n/*\n *\t${file}\n */\n`
}
output_txt += file_content;
output_txt = output_txt.replace(/['"]use strict['"];/, '');
}
......@@ -127,13 +120,47 @@ function pack(output_path, inputs, minify) {
}
}
function get_compiled_file(file, output_path, minify, force_compile) {
const output_type = output_path.split('.').pop();
let file_content;
if (force_compile === false) {
// force compile is false
// attempt to get from cache
file_content = compiled_js_cache[file];
if (file_content) {
return file_content;
}
}
file_content = fs.readFileSync(file, 'utf-8');
if (file.endsWith('.html') && output_type === 'js') {
file_content = html_to_js_template(file, file_content);
}
if(file.endsWith('class.js')) {
file_content = minify_js(file_content, file);
}
if (minify && file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
file_content = babelify(file_content, file, minify);
}
compiled_js_cache[file] = file_content;
return file_content;
}
function babelify(content, path, minify) {
let presets = ['env'];
var plugins = ['transform-object-rest-spread']
// Minification doesn't work when loading Frappe Desk
// Avoid for now, trace the error and come back.
try {
return babel.transform(content, {
presets: presets,
plugins: plugins,
comments: false
}).code;
} catch (e) {
......@@ -258,16 +285,16 @@ function watch_less(ondirty) {
}
function watch_js(ondirty) {
const js_paths = app_paths.map(path => path_join(path, 'public', 'js'));
const to_watch = filter_valid_paths(js_paths);
chokidar.watch(to_watch).on('change', (filename, stats) => {
console.log(filename, 'dirty');
chokidar.watch([
path_join(apps_path, '**', '*.js'),
path_join(apps_path, '**', '*.html')
]).on('change', (filename) => {
// build the target js file for which this js/html file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(filename)) {
pack(target, sources);
console.log(filename, 'dirty');
pack(target, sources, null, filename);
ondirty && ondirty(target);
// break;
}
......
- Enhanced Data Import Tool
- Data Import Tool is now a normal form, you can maintain records for each Data Import.
- Better error handling
- Background processing for large files
- Frappé now has a github connector
- Any doctype can have a calendar view
- Frappé has a new simple, responsive, modern SVG [charts library](https://github.com/frappe/charts), developed by us
\ No newline at end of file
......@@ -3,7 +3,6 @@ import click
import hashlib, os, sys, compileall
import frappe
from frappe import _
from _mysql_exceptions import ProgrammingError
from frappe.commands import pass_context, get_site
from frappe.commands.scheduler import _is_scheduler_enabled
from frappe.limits import update_limits, get_limits
......@@ -11,6 +10,12 @@ from frappe.installer import update_site_config
from frappe.utils import touch_file, get_site_path
from six import text_type
# imports - third-party imports
from pymysql.constants import ER
# imports - module imports
from frappe.exceptions import SQLError
@click.command('new-site')
@click.argument('site')
@click.option('--db-name', help='Database name')
......@@ -348,8 +353,8 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path=
try:
scheduled_backup(ignore_files=False, force=True)
except ProgrammingError as err:
if err[0] == 1146:
except SQLError as err:
if err[0] == ER.NO_SUCH_TABLE:
if force:
pass
else:
......@@ -400,8 +405,9 @@ def move(dest_dir, site):
@click.command('set-admin-password')
@click.argument('admin-password')
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False)
@pass_context
def set_admin_password(context, admin_password):
def set_admin_password(context, admin_password, logout_all_sessions=False):
"Set Administrator password for a site"
import getpass
from frappe.utils.password import update_password
......@@ -414,7 +420,7 @@ def set_admin_password(context, admin_password):
admin_password = getpass.getpass("Administrator's password for {0}: ".format(site))
frappe.connect()
update_password('Administrator', admin_password)
update_password(user='Administrator', pwd=admin_password, logout_all_sessions=logout_all_sessions)
frappe.db.commit()
admin_password = None
finally:
......
......@@ -15,7 +15,9 @@ def build(make_copy=False, restore = False, verbose=False):
import frappe.build
import frappe
frappe.init('')
frappe.build.bundle(False, make_copy=make_copy, restore = restore, verbose=verbose)
# don't minify in developer_mode for faster builds
no_compress = frappe.local.conf.developer_mode or False
frappe.build.bundle(no_compress, make_copy=make_copy, restore = restore, verbose=verbose)
@click.command('watch')
def watch():
......@@ -162,12 +164,12 @@ def export_doc(context, doctype, docname):
@pass_context
def export_json(context, doctype, path, name=None):
"Export doclist as json to the given path, use '-' as name for Singles."
from frappe.core.page.data_import_tool import data_import_tool
from frappe.core.doctype.data_import import data_import
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
data_import_tool.export_json(doctype, path, name=name)
data_import.export_json(doctype, path, name=name)
finally:
frappe.destroy()
......@@ -177,12 +179,12 @@ def export_json(context, doctype, path, name=None):
@pass_context
def export_csv(context, doctype, path):
"Export data import template with data for DocType"
from frappe.core.page.data_import_tool import data_import_tool
from frappe.core.doctype.data_import import data_import
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
data_import_tool.export_csv(doctype, path)
data_import.export_csv(doctype, path)
finally:
frappe.destroy()
......@@ -204,7 +206,7 @@ def export_fixtures(context):
@pass_context
def import_doc(context, path, force=False):
"Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported"
from frappe.core.page.data_import_tool import data_import_tool
from frappe.core.doctype.data_import import data_import
if not os.path.exists(path):
path = os.path.join('..', path)
......@@ -216,7 +218,7 @@ def import_doc(context, path, force=False):
try:
frappe.init(site=site)
frappe.connect()
data_import_tool.import_doc(path, overwrite=context.force)
data_import.import_doc(path, overwrite=context.force)
finally:
frappe.destroy()
......@@ -229,8 +231,8 @@ def import_doc(context, path, force=False):
@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 tool"
from frappe.core.page.data_import_tool import importer
"Import CSV using data import"
from frappe.core.doctype.data_import import importer
from frappe.utils.csvutils import read_csv_content
site = get_site(context)
......@@ -300,6 +302,7 @@ def console(context):
@click.command('run-tests')
@click.option('--app', help="For App")
@click.option('--doctype', help="For DocType")
@click.option('--doctype-list-path', help="Path to .txt file for list of doctypes. Example erpnext/tests/server/agriculture.txt")
@click.option('--test', multiple=True, help="Specific test")
@click.option('--driver', help="For Travis")
@click.option('--ui-tests', is_flag=True, default=False, help="Run UI Tests")
......@@ -308,7 +311,7 @@ def console(context):
@click.option('--junit-xml-output', help="Destination file path for junit xml report")
@pass_context
def run_tests(context, app=None, module=None, doctype=None, test=(),
driver=None, profile=False, junit_xml_output=False, ui_tests = False):
driver=None, profile=False, junit_xml_output=False, ui_tests = False, doctype_list_path=None):
"Run tests"
import frappe.test_runner
tests = test
......@@ -318,7 +321,7 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
ret = frappe.test_runner.main(app, module, doctype, context.verbose, tests=tests,
force=context.force, profile=profile, junit_xml_output=junit_xml_output,
ui_tests = ui_tests)
ui_tests = ui_tests, doctype_list_path = doctype_list_path)
if len(ret.failures) == 0 and len(ret.errors) == 0:
ret = 0
......@@ -327,10 +330,11 @@ def run_tests(context, app=None, module=None, doctype=None, test=(),
@click.command('run-ui-tests')
@click.option('--app', help="App to run tests on, leave blank for all apps")
@click.option('--test', help="File name of the test you want to run")
@click.option('--test', help="Path to the specific test you want to run")
@click.option('--test-list', help="Path to the txt file with the list of test cases")
@click.option('--profile', is_flag=True, default=False)
@pass_context
def run_ui_tests(context, app=None, test=False, profile=False):
def run_ui_tests(context, app=None, test=False, test_list=False, profile=False):
"Run UI tests"
import frappe.test_runner
......@@ -338,7 +342,7 @@ def run_ui_tests(context, app=None, test=False, profile=False):
frappe.init(site=site)
frappe.connect()
ret = frappe.test_runner.run_ui_tests(app=app, test=test, verbose=context.verbose,
ret = frappe.test_runner.run_ui_tests(app=app, test=test, test_list=test_list, verbose=context.verbose,
profile=profile)
if len(ret.failures) == 0 and len(ret.errors) == 0:
ret = 0
......
......@@ -70,5 +70,5 @@ def get_data():
"icon": "octicon octicon-book",
"color": '#FFAEDB',
"hidden": 1,
},
}
]
......@@ -82,7 +82,16 @@ def get_data():
"name": "Webhook",
"description": _("Webhooks calling API requests into web apps"),
}
]
},
{
"label": _("Maps"),
"items": [
{
"type": "doctype",
"name": "Google Maps",
"description": _("Google Maps integration"),
}
]
}
]
......@@ -17,6 +17,11 @@ def get_data():
"type": "doctype",
"name": "Role",
"description": _("User Roles")
},
{
"type": "doctype",
"name": "Role Profile",
"description": _("Role Profile")
}
]
},
......@@ -81,6 +86,13 @@ def get_data():
"name": "Error Snapshot",
"description": _("Log of error during requests.")
},
{
"type": "doctype",
"name": "Domain Settings",
"label": _("Domain Settings"),
"description": _("Enable / Disable Domains"),
"hide_count": True
},
]
},
{
......@@ -88,11 +100,11 @@ def get_data():
"icon": "fa fa-th",
"items": [
{
"type": "page",
"name": "data-import-tool",
"type": "doctype",
"name": "Data Import",
"label": _("Import / Export Data"),
"icon": "fa fa-upload",
"description": _("Import / Export Data from .csv files.")
"icon": "octicon octicon-cloud-upload",
"description": _("Import / Export Data from CSV and Excel files.")
},
{
"type": "doctype",
......
......@@ -26,18 +26,17 @@ def load_address_and_contact(doc, key=None):
doc.set_onload('addr_list', address_list)
contact_list = []
if doc.doctype != "Lead":
filters = [
["Dynamic Link", "link_doctype", "=", doc.doctype],
["Dynamic Link", "link_name", "=", doc.name],
["Dynamic Link", "parenttype", "=", "Contact"],
]
contact_list = frappe.get_all("Contact", filters=filters, fields=["*"])
contact_list = sorted(contact_list,
lambda a, b:
(int(a.is_primary_contact - b.is_primary_contact)) or
(1 if a.modified - b.modified else 0), reverse=True)
filters = [
["Dynamic Link", "link_doctype", "=", doc.doctype],
["Dynamic Link", "link_name", "=", doc.name],
["Dynamic Link", "parenttype", "=", "Contact"],
]
contact_list = frappe.get_all("Contact", filters=filters, fields=["*"])
contact_list = sorted(contact_list,
lambda a, b:
(int(a.is_primary_contact - b.is_primary_contact)) or
(1 if a.modified - b.modified else 0), reverse=True)
doc.set_onload('contact_list', contact_list)
......@@ -147,4 +146,4 @@ def filter_dynamic_link_doctypes(doctype, txt, searchfield, start, page_len, fil
order_by="dt asc", as_list=True)
all_doctypes = doctypes + _doctypes
return sorted(all_doctypes, key=lambda item: item[0])
\ No newline at end of file
return sorted(all_doctypes, key=lambda item: item[0])
......@@ -46,6 +46,11 @@ class Contact(Document):
return None
def has_link(self, doctype, name):
for link in self.links:
if link.link_doctype==doctype and link.link_name== name:
return True
def has_common_link(self, doc):
reference_links = [(link.link_doctype, link.link_name) for link in doc.links]
for link in self.links:
......
......@@ -15,15 +15,14 @@ frappe.query_reports["Addresses And Contacts"] = {
"name": ["in","Customer,Supplier,Sales Partner"],
}
}
},
"default": "Customer"
}
},
{
"fieldname":"party_name",
"label": __("Party Name"),
"fieldtype": "Dynamic Link",
"get_options": function() {
var party_type = frappe.query_report_filters_by_name.party_type.get_value();
let party_type = frappe.query_report_filters_by_name.party_type.get_value();
if(!party_type) {
frappe.throw(__("Please select Party Type first"));
}
......
// Copyright (c) 2016, Frappe Technologies and contributors
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Authentication Log', {
refresh: function(frm) {
frappe.ui.form.on('Activity Log', {
refresh: function() {
}
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment