* [refactor] domain and domain settings * [fix] test_domain.py * [fix] patches * [fix] domain activation after setup * [fix] tests and lint * [fix] tests and lint * [enhance] better prompt naming * [fix] setup wizard test * [fix] testing * [minor] new item in quick entry from form dashboardversion-14
@@ -475,13 +475,26 @@ def only_for(roles): | |||||
if not roles.intersection(myroles): | if not roles.intersection(myroles): | ||||
raise PermissionError | raise PermissionError | ||||
def get_domain_data(module): | |||||
try: | |||||
domain_data = get_hooks('domains') | |||||
if module in domain_data: | |||||
return _dict(get_attr(get_hooks('domains')[module][0] + '.data')) | |||||
else: | |||||
return _dict() | |||||
except ImportError: | |||||
if local.flags.in_test: | |||||
return _dict() | |||||
else: | |||||
raise | |||||
def clear_cache(user=None, doctype=None): | def clear_cache(user=None, doctype=None): | ||||
"""Clear **User**, **DocType** or global cache. | """Clear **User**, **DocType** or global cache. | ||||
:param user: If user is given, only user cache is cleared. | :param user: If user is given, only user cache is cleared. | ||||
:param doctype: If doctype is given, only DocType cache is cleared.""" | :param doctype: If doctype is given, only DocType cache is cleared.""" | ||||
import frappe.sessions | import frappe.sessions | ||||
from frappe.core.doctype.domain_settings.domain_settings import clear_domain_cache | |||||
if doctype: | if doctype: | ||||
import frappe.model.meta | import frappe.model.meta | ||||
frappe.model.meta.clear_cache(doctype) | frappe.model.meta.clear_cache(doctype) | ||||
@@ -493,7 +506,6 @@ def clear_cache(user=None, doctype=None): | |||||
frappe.sessions.clear_cache() | frappe.sessions.clear_cache() | ||||
translate.clear_cache() | translate.clear_cache() | ||||
reset_metadata_version() | reset_metadata_version() | ||||
clear_domain_cache() | |||||
local.cache = {} | local.cache = {} | ||||
local.new_doc_templates = {} | local.new_doc_templates = {} | ||||
@@ -60,11 +60,11 @@ function watch() { | |||||
io.emit('reload_css', filename); | 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(); | watch_build_json(); | ||||
}); | }); | ||||
@@ -4,7 +4,89 @@ | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe | import frappe | ||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields | |||||
class Domain(Document): | class Domain(Document): | ||||
pass | |||||
'''Domain documents are created automatically when DocTypes | |||||
with "Restricted" domains are imported during | |||||
installation or migration''' | |||||
def setup_domain(self): | |||||
'''Setup domain icons, permissions, custom fields etc.''' | |||||
self.setup_data() | |||||
self.setup_roles() | |||||
self.setup_properties() | |||||
self.set_values() | |||||
if not int(frappe.db.get_single_value('System Settings', 'setup_complete') or 0): | |||||
# if setup not complete, setup desktop etc. | |||||
self.setup_sidebar_items() | |||||
self.setup_desktop_icons() | |||||
self.set_default_portal_role() | |||||
if self.data.custom_fields: | |||||
create_custom_fields(self.data.custom_fields) | |||||
if self.data.on_setup: | |||||
# custom on_setup method | |||||
frappe.get_attr(self.data.on_setup)() | |||||
def setup_roles(self): | |||||
'''Enable roles that are restricted to this domain''' | |||||
if self.data.restricted_roles: | |||||
for role_name in self.data.restricted_roles: | |||||
role = frappe.get_doc('Role', role_name) | |||||
role.disabled = 0 | |||||
role.save() | |||||
def setup_data(self, domain=None): | |||||
'''Load domain info via hooks''' | |||||
self.data = frappe.get_domain_data(self.name) | |||||
def get_domain_data(self, module): | |||||
return frappe.get_attr(frappe.get_hooks('domains')[self.name] + '.data') | |||||
def set_default_portal_role(self): | |||||
'''Set default portal role based on domain''' | |||||
if self.data.get('default_portal_role'): | |||||
frappe.db.set_value('Portal Settings', None, 'default_role', | |||||
self.data.get('default_portal_role')) | |||||
def setup_desktop_icons(self): | |||||
'''set desktop icons form `data.desktop_icons`''' | |||||
from frappe.desk.doctype.desktop_icon.desktop_icon import set_desktop_icons | |||||
if self.data.desktop_icons: | |||||
set_desktop_icons(self.data.desktop_icons) | |||||
def setup_properties(self): | |||||
if self.data.properties: | |||||
for args in self.data.properties: | |||||
frappe.make_property_setter(args) | |||||
def set_values(self): | |||||
'''set values based on `data.set_value`''' | |||||
if self.data.set_value: | |||||
for args in self.data.set_value: | |||||
doc = frappe.get_doc(args[0], args[1] or args[0]) | |||||
doc.set(args[2], args[3]) | |||||
doc.save() | |||||
def setup_sidebar_items(self): | |||||
'''Enable / disable sidebar items''' | |||||
if self.data.allow_sidebar_items: | |||||
# disable all | |||||
frappe.db.sql('update `tabPortal Menu Item` set enabled=0') | |||||
# enable | |||||
frappe.db.sql('''update `tabPortal Menu Item` set enabled=1 | |||||
where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.allow_sidebar_items]))) | |||||
if self.data.remove_sidebar_items: | |||||
# disable all | |||||
frappe.db.sql('update `tabPortal Menu Item` set enabled=1') | |||||
# enable | |||||
frappe.db.sql('''update `tabPortal Menu Item` set enabled=0 | |||||
where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in self.data.remove_sidebar_items]))) |
@@ -7,8 +7,46 @@ import frappe | |||||
from frappe.model.document import Document | from frappe.model.document import Document | ||||
class DomainSettings(Document): | class DomainSettings(Document): | ||||
def set_active_domains(self, domains): | |||||
self.active_domains = [] | |||||
for d in domains: | |||||
self.append('active_domains', dict(domain=d)) | |||||
self.save() | |||||
def on_update(self): | def on_update(self): | ||||
clear_domain_cache() | |||||
for d in self.active_domains: | |||||
domain = frappe.get_doc('Domain', d.domain) | |||||
domain.setup_domain() | |||||
self.restrict_roles_and_modules() | |||||
frappe.clear_cache() | |||||
def restrict_roles_and_modules(self): | |||||
'''Disable all restricted roles and set `restrict_to_domain` property in Module Def''' | |||||
active_domains = frappe.get_active_domains() | |||||
all_domains = (frappe.get_hooks('domains') or {}).keys() | |||||
def remove_role(role): | |||||
frappe.db.sql('delete from `tabHas Role` where role=%s', role) | |||||
frappe.set_value('Role', role, 'disabled', 1) | |||||
for domain in all_domains: | |||||
data = frappe.get_domain_data(domain) | |||||
if not frappe.db.get_value('Domain', domain): | |||||
frappe.get_doc(dict(doctype='Domain', domain=domain)).insert() | |||||
if 'modules' in data: | |||||
for module in data.get('modules'): | |||||
frappe.db.set_value('Module Def', module, 'restrict_to_domain', domain) | |||||
if 'restricted_roles' in data: | |||||
for role in data['restricted_roles']: | |||||
if not frappe.db.get_value('Role', role): | |||||
frappe.get_doc(dict(doctype='Role', role_name=role)).insert() | |||||
frappe.db.set_value('Role', role, 'restrict_to_domain', domain) | |||||
if domain not in active_domains: | |||||
remove_role(role) | |||||
def get_active_domains(): | def get_active_domains(): | ||||
""" get the domains set in the Domain Settings as active domain """ | """ get the domains set in the Domain Settings as active domain """ | ||||
@@ -33,6 +71,3 @@ def get_active_modules(): | |||||
return active_modules | return active_modules | ||||
return frappe.cache().get_value('active_modules', _get_active_modules) | return frappe.cache().get_value('active_modules', _get_active_modules) | ||||
def clear_domain_cache(): | |||||
frappe.cache().delete_key(['active_domains', 'active_modules']) |
@@ -14,7 +14,7 @@ from frappe.twofactor import toggle_two_factor_auth | |||||
class SystemSettings(Document): | class SystemSettings(Document): | ||||
def validate(self): | def validate(self): | ||||
enable_password_policy = cint(self.enable_password_policy) and True or False | enable_password_policy = cint(self.enable_password_policy) and True or False | ||||
minimum_password_score = cint(self.minimum_password_score) or 0 | |||||
minimum_password_score = cint(getattr(self, 'minimum_password_score', 0)) or 0 | |||||
if enable_password_policy and minimum_password_score <= 0: | if enable_password_policy and minimum_password_score <= 0: | ||||
frappe.throw(_("Please select Minimum Password Score")) | frappe.throw(_("Please select Minimum Password Score")) | ||||
elif not enable_password_policy: | elif not enable_password_policy: | ||||
@@ -111,6 +111,10 @@ def create_custom_fields(custom_fields): | |||||
:param custom_fields: example `{'Sales Invoice': [dict(fieldname='test')]}`''' | :param custom_fields: example `{'Sales Invoice': [dict(fieldname='test')]}`''' | ||||
for doctype, fields in custom_fields.items(): | for doctype, fields in custom_fields.items(): | ||||
if isinstance(fields, dict): | |||||
# only one field | |||||
fields = [fields] | |||||
for df in fields: | for df in fields: | ||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]}) | field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": df["fieldname"]}) | ||||
if not field: | if not field: | ||||
@@ -508,19 +508,22 @@ frappe.setup.utils = { | |||||
bind_language_events: function(slide) { | bind_language_events: function(slide) { | ||||
slide.get_input("language").unbind("change").on("change", function() { | slide.get_input("language").unbind("change").on("change", function() { | ||||
var lang = $(this).val() || "English"; | |||||
frappe._messages = {}; | |||||
frappe.call({ | |||||
method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages", | |||||
freeze: true, | |||||
args: { | |||||
language: lang | |||||
}, | |||||
callback: function(r) { | |||||
frappe.setup._from_load_messages = true; | |||||
frappe.wizard.refresh_slides(); | |||||
} | |||||
}); | |||||
clearTimeout (slide.language_call_timeout); | |||||
slide.language_call_timeout = setTimeout (() => { | |||||
var lang = $(this).val() || "English"; | |||||
frappe._messages = {}; | |||||
frappe.call({ | |||||
method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages", | |||||
freeze: true, | |||||
args: { | |||||
language: lang | |||||
}, | |||||
callback: function(r) { | |||||
frappe.setup._from_load_messages = true; | |||||
frappe.wizard.refresh_slides(); | |||||
} | |||||
}); | |||||
}, 500); | |||||
}); | }); | ||||
}, | }, | ||||
@@ -352,7 +352,7 @@ def get_filters_cond(doctype, filters, conditions, ignore_permissions=None, with | |||||
for f in filters: | for f in filters: | ||||
if isinstance(f[1], string_types) and f[1][0] == '!': | if isinstance(f[1], string_types) and f[1][0] == '!': | ||||
flt.append([doctype, f[0], '!=', f[1][1:]]) | flt.append([doctype, f[0], '!=', f[1][1:]]) | ||||
elif isinstance(f[1], list) and \ | |||||
elif isinstance(f[1], (list, tuple)) and \ | |||||
f[1][0] in (">", "<", ">=", "<=", "like", "not like", "in", "not in", "between"): | f[1][0] in (">", "<", ">=", "<=", "like", "not like", "in", "not in", "between"): | ||||
flt.append([doctype, f[0], f[1][0], f[1][1]]) | flt.append([doctype, f[0], f[1][0], f[1][1]]) | ||||
@@ -7,10 +7,11 @@ frappe.patches.v7_1.rename_scheduler_log_to_error_log | |||||
frappe.patches.v6_1.rename_file_data | frappe.patches.v6_1.rename_file_data | ||||
frappe.patches.v7_0.re_route #2016-06-27 | frappe.patches.v7_0.re_route #2016-06-27 | ||||
frappe.patches.v7_2.remove_in_filter | frappe.patches.v7_2.remove_in_filter | ||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-03-09 | |||||
frappe.patches.v8_0.drop_in_dialog | |||||
frappe.patches.v8_0.drop_in_dialog #2017-09-22 | |||||
execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2017-09-22 | |||||
execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2017-03-03 | execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2017-03-03 | ||||
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2017-03-03 | execute:frappe.reload_doc('core', 'doctype', 'docperm') #2017-03-03 | ||||
execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22 | |||||
frappe.patches.v8_0.drop_is_custom_from_docperm | frappe.patches.v8_0.drop_is_custom_from_docperm | ||||
frappe.patches.v8_0.update_records_in_global_search #11-05-2017 | frappe.patches.v8_0.update_records_in_global_search #11-05-2017 | ||||
frappe.patches.v8_0.update_published_in_global_search | frappe.patches.v8_0.update_published_in_global_search | ||||
@@ -2,6 +2,13 @@ | |||||
// MIT License. See license.txt | // MIT License. See license.txt | ||||
frappe.db = { | frappe.db = { | ||||
exists: function(doctype, name) { | |||||
return new Promise ((resolve) => { | |||||
frappe.db.get_value(doctype, {name: name}, 'name').then((r) => { | |||||
(r.message && r.message.name) ? resolve(true) : resolve(false); | |||||
}); | |||||
}); | |||||
}, | |||||
get_value: function(doctype, filters, fieldname, callback) { | get_value: function(doctype, filters, fieldname, callback) { | ||||
return frappe.call({ | return frappe.call({ | ||||
method: "frappe.client.get_value", | method: "frappe.client.get_value", | ||||
@@ -47,6 +47,10 @@ frappe.ui.form.Control = Class.extend({ | |||||
// returns "Read", "Write" or "None" | // returns "Read", "Write" or "None" | ||||
// as strings based on permissions | // as strings based on permissions | ||||
get_status: function(explain) { | get_status: function(explain) { | ||||
if (this.df.get_status) { | |||||
return this.df.get_status(this); | |||||
} | |||||
if(!this.doctype && !this.docname) { | if(!this.doctype && !this.docname) { | ||||
// like in case of a dialog box | // like in case of a dialog box | ||||
if (cint(this.df.hidden)) { | if (cint(this.df.hidden)) { | ||||
@@ -71,7 +71,7 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ | |||||
} | } | ||||
}; | }; | ||||
if(me.disp_status != "None") { | |||||
if (me.disp_status != "None") { | |||||
// refresh value | // refresh value | ||||
if(me.doctype && me.docname) { | if(me.doctype && me.docname) { | ||||
me.value = frappe.model.get_value(me.doctype, me.docname, me.df.fieldname); | me.value = frappe.model.get_value(me.doctype, me.docname, me.df.fieldname); | ||||
@@ -28,9 +28,9 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ | |||||
setup_autoname_check: function() { | setup_autoname_check: function() { | ||||
if (!this.df.parent) return; | if (!this.df.parent) return; | ||||
this.meta = frappe.get_meta(this.df.parent); | this.meta = frappe.get_meta(this.df.parent); | ||||
if (this.meta && this.meta.autoname | |||||
if (this.meta && ((this.meta.autoname | |||||
&& this.meta.autoname.substr(0, 6)==='field:' | && this.meta.autoname.substr(0, 6)==='field:' | ||||
&& this.meta.autoname.substr(6) === this.df.fieldname) { | |||||
&& this.meta.autoname.substr(6) === this.df.fieldname) || this.df.fieldname==='__newname') ) { | |||||
this.$input.on('keyup', () => { | this.$input.on('keyup', () => { | ||||
this.set_description(''); | this.set_description(''); | ||||
if (this.doc && this.doc.__islocal) { | if (this.doc && this.doc.__islocal) { | ||||
@@ -12,13 +12,11 @@ frappe.ui.form.ControlPassword = frappe.ui.form.ControlData.extend({ | |||||
this.indicator = this.$wrapper.find('.password-strength-indicator'); | this.indicator = this.$wrapper.find('.password-strength-indicator'); | ||||
this.message = this.$wrapper.find('.help-box'); | this.message = this.$wrapper.find('.help-box'); | ||||
this.$input.on('input', () => { | |||||
var $this = $(this); | |||||
clearTimeout($this.data('timeout')); | |||||
$this.data('timeout', setTimeout(() => { | |||||
var txt = me.$input.val(); | |||||
me.get_password_strength(txt); | |||||
}), 300); | |||||
this.$input.on('keyup', () => { | |||||
clearTimeout(this.check_password_timeout); | |||||
this.check_password_timeout = setTimeout (() => { | |||||
me.get_password_strength(me.$input.val()); | |||||
}, 500); | |||||
}); | }); | ||||
}, | }, | ||||
get_password_strength: function(value) { | get_password_strength: function(value) { | ||||
@@ -25,7 +25,7 @@ frappe.ui.form.Layout = Class.extend({ | |||||
this.wrapper = $('<div class="form-layout">').appendTo(this.parent); | this.wrapper = $('<div class="form-layout">').appendTo(this.parent); | ||||
this.message = $('<div class="form-message text-muted small hidden"></div>').appendTo(this.wrapper); | this.message = $('<div class="form-message text-muted small hidden"></div>').appendTo(this.wrapper); | ||||
if(!this.fields) { | if(!this.fields) { | ||||
this.fields = frappe.meta.sort_docfields(frappe.meta.docfield_map[this.doctype]); | |||||
this.fields = this.get_doctype_fields(); | |||||
} | } | ||||
this.setup_tabbing(); | this.setup_tabbing(); | ||||
this.render(); | this.render(); | ||||
@@ -35,6 +35,28 @@ frappe.ui.form.Layout = Class.extend({ | |||||
this.show_message(__("This form does not have any input")); | this.show_message(__("This form does not have any input")); | ||||
} | } | ||||
}, | }, | ||||
get_doctype_fields: function() { | |||||
let fields = [ | |||||
{ | |||||
parent: this.frm.doctype, | |||||
fieldtype: 'Data', | |||||
fieldname: '__newname', | |||||
reqd: 1, | |||||
hidden: 1, | |||||
label: __('Name'), | |||||
get_status: function(field) { | |||||
if (field.frm && field.frm.is_new() | |||||
&& field.frm.meta.autoname | |||||
&& ['prompt', 'name'].includes(field.frm.meta.autoname.toLowerCase())) { | |||||
return 'Write'; | |||||
} | |||||
return 'None'; | |||||
} | |||||
} | |||||
]; | |||||
fields = fields.concat(frappe.meta.sort_docfields(frappe.meta.docfield_map[this.doctype])); | |||||
return fields; | |||||
}, | |||||
show_message: function(html) { | show_message: function(html) { | ||||
if(html) { | if(html) { | ||||
if(html.substr(0, 1)!=='<') { | if(html.substr(0, 1)!=='<') { | ||||
@@ -1,6 +1,6 @@ | |||||
frappe.provide('frappe.ui.form'); | frappe.provide('frappe.ui.form'); | ||||
frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback) => { | |||||
frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback, doc) => { | |||||
var trimmed_doctype = doctype.replace(/ /g, ''); | var trimmed_doctype = doctype.replace(/ /g, ''); | ||||
var controller_name = "QuickEntryForm"; | var controller_name = "QuickEntryForm"; | ||||
@@ -8,15 +8,16 @@ frappe.ui.form.make_quick_entry = (doctype, after_insert, init_callback) => { | |||||
controller_name = trimmed_doctype + "QuickEntryForm"; | controller_name = trimmed_doctype + "QuickEntryForm"; | ||||
} | } | ||||
frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert, init_callback); | |||||
frappe.quick_entry = new frappe.ui.form[controller_name](doctype, after_insert, init_callback, doc); | |||||
return frappe.quick_entry.setup(); | return frappe.quick_entry.setup(); | ||||
}; | }; | ||||
frappe.ui.form.QuickEntryForm = Class.extend({ | frappe.ui.form.QuickEntryForm = Class.extend({ | ||||
init: function(doctype, after_insert, init_callback){ | |||||
init: function(doctype, after_insert, init_callback, doc) { | |||||
this.doctype = doctype; | this.doctype = doctype; | ||||
this.after_insert = after_insert; | this.after_insert = after_insert; | ||||
this.init_callback = init_callback; | this.init_callback = init_callback; | ||||
this.doc = doc; | |||||
}, | }, | ||||
setup: function() { | setup: function() { | ||||
@@ -40,7 +41,9 @@ frappe.ui.form.QuickEntryForm = Class.extend({ | |||||
this.mandatory = $.map(frappe.get_meta(this.doctype).fields, | this.mandatory = $.map(frappe.get_meta(this.doctype).fields, | ||||
function(d) { return (d.reqd || d.bold && !d.read_only) ? d : null; }); | function(d) { return (d.reqd || d.bold && !d.read_only) ? d : null; }); | ||||
this.meta = frappe.get_meta(this.doctype); | this.meta = frappe.get_meta(this.doctype); | ||||
this.doc = frappe.model.get_new_doc(this.doctype, null, null, true); | |||||
if (!this.doc) { | |||||
this.doc = frappe.model.get_new_doc(this.doctype, null, null, true); | |||||
} | |||||
}, | }, | ||||
is_quick_entry: function(){ | is_quick_entry: function(){ | ||||
@@ -108,7 +111,7 @@ frappe.ui.form.QuickEntryForm = Class.extend({ | |||||
this.dialog.onhide = () => frappe.quick_entry = null; | this.dialog.onhide = () => frappe.quick_entry = null; | ||||
this.dialog.show(); | this.dialog.show(); | ||||
this.set_defaults(); | this.set_defaults(); | ||||
if (this.init_callback) { | if (this.init_callback) { | ||||
this.init_callback(this.dialog); | this.init_callback(this.dialog); | ||||
} | } | ||||
@@ -19,27 +19,24 @@ frappe.ui.form.save = function (frm, action, callback, btn) { | |||||
var save = function () { | var save = function () { | ||||
remove_empty_rows(); | remove_empty_rows(); | ||||
check_name(function () { | |||||
$(frm.wrapper).addClass('validated-form'); | |||||
if (check_mandatory()) { | |||||
_call({ | |||||
method: "frappe.desk.form.save.savedocs", | |||||
args: { doc: frm.doc, action: action }, | |||||
callback: function (r) { | |||||
$(document).trigger("save", [frm.doc]); | |||||
callback(r); | |||||
}, | |||||
error: function (r) { | |||||
callback(r); | |||||
}, | |||||
btn: btn, | |||||
freeze_message: freeze_message | |||||
}); | |||||
} else { | |||||
$(btn).prop("disabled", false); | |||||
} | |||||
}); | |||||
$(frm.wrapper).addClass('validated-form'); | |||||
if (check_mandatory()) { | |||||
_call({ | |||||
method: "frappe.desk.form.save.savedocs", | |||||
args: { doc: frm.doc, action: action }, | |||||
callback: function (r) { | |||||
$(document).trigger("save", [frm.doc]); | |||||
callback(r); | |||||
}, | |||||
error: function (r) { | |||||
callback(r); | |||||
}, | |||||
btn: btn, | |||||
freeze_message: freeze_message | |||||
}); | |||||
} else { | |||||
$(btn).prop("disabled", false); | |||||
} | |||||
}; | }; | ||||
var remove_empty_rows = function() { | var remove_empty_rows = function() { | ||||
@@ -107,36 +104,6 @@ frappe.ui.form.save = function (frm, action, callback, btn) { | |||||
}); | }); | ||||
}; | }; | ||||
var check_name = function (callback) { | |||||
var doc = frm.doc; | |||||
var meta = locals.DocType[doc.doctype]; | |||||
if (doc.__islocal && (meta && meta.autoname | |||||
&& meta.autoname.toLowerCase() == 'prompt')) { | |||||
var d = frappe.prompt(__("Name"), function (values) { | |||||
var newname = values.value; | |||||
if (newname) { | |||||
doc.__newname = strip(newname); | |||||
} else { | |||||
frappe.msgprint(__("Name is required")); | |||||
throw "name required"; | |||||
} | |||||
callback(); | |||||
}, __('Enter the name of the new {0}', [doc.doctype]), __("Create")); | |||||
if (doc.__newname) { | |||||
d.set_value("value", doc.__newname); | |||||
} | |||||
d.onhide = function () { | |||||
$(btn).prop("disabled", false); | |||||
} | |||||
} else { | |||||
callback(); | |||||
} | |||||
}; | |||||
var check_mandatory = function () { | var check_mandatory = function () { | ||||
var me = this; | var me = this; | ||||
var has_errors = false; | var has_errors = false; | ||||
@@ -50,6 +50,15 @@ frappe.avatar = function(user, css_class, title) { | |||||
} | } | ||||
} | } | ||||
frappe.ui.scroll = function(element, animate, additional_offset) { | |||||
var header_offset = $(".navbar").height() + $(".page-head").height(); | |||||
var top = $(element).offset().top - header_offset - cint(additional_offset); | |||||
if (animate) { | |||||
$("html, body").animate({ scrollTop: top }); | |||||
} else { | |||||
$(window).scrollTop(top); | |||||
} | |||||
}; | |||||
frappe.get_palette = function(txt) { | frappe.get_palette = function(txt) { | ||||
return '#fafbfc'; | return '#fafbfc'; | ||||
@@ -327,17 +327,23 @@ $.extend(frappe.model, { | |||||
set_value: function(doctype, docname, fieldname, value, fieldtype) { | set_value: function(doctype, docname, fieldname, value, fieldtype) { | ||||
/* help: Set a value locally (if changed) and execute triggers */ | /* help: Set a value locally (if changed) and execute triggers */ | ||||
var doc = locals[doctype] && locals[doctype][docname]; | |||||
var doc; | |||||
if ($.isPlainObject(doctype)) { | |||||
// first parameter is the doc, shift parameters to the left | |||||
doc = doctype; fieldname = docname; value = fieldname; | |||||
} else { | |||||
doc = locals[doctype] && locals[doctype][docname]; | |||||
} | |||||
var to_update = fieldname; | |||||
let to_update = fieldname; | |||||
let tasks = []; | let tasks = []; | ||||
if(!$.isPlainObject(to_update)) { | if(!$.isPlainObject(to_update)) { | ||||
to_update = {}; | to_update = {}; | ||||
to_update[fieldname] = value; | to_update[fieldname] = value; | ||||
} | } | ||||
$.each(to_update, function(key, value) { | |||||
if(doc && doc[key] !== value) { | |||||
$.each(to_update, (key, value) => { | |||||
if (doc && doc[key] !== value) { | |||||
if(doc.__unedited && !(!doc[key] && !value)) { | if(doc.__unedited && !(!doc[key] && !value)) { | ||||
// unset unedited flag for virgin rows | // unset unedited flag for virgin rows | ||||
doc.__unedited = false; | doc.__unedited = false; | ||||
@@ -145,6 +145,7 @@ frappe.request.call = function(opts) { | |||||
frappe.msgprint({message:__("Server Error: Please check your server logs or contact tech support."), title:__('Something went wrong'), indicator: 'red'}); | frappe.msgprint({message:__("Server Error: Please check your server logs or contact tech support."), title:__('Something went wrong'), indicator: 'red'}); | ||||
try { | try { | ||||
opts.error_callback && opts.error_callback(); | opts.error_callback && opts.error_callback(); | ||||
frappe.request.report_error(xhr, opts); | |||||
} catch (e) { | } catch (e) { | ||||
frappe.request.report_error(xhr, opts); | frappe.request.report_error(xhr, opts); | ||||
} | } | ||||
@@ -222,17 +222,19 @@ frappe.socketio = { | |||||
}, 5); | }, 5); | ||||
}); | }); | ||||
// js files show alert | // js files show alert | ||||
frappe.socketio.file_watcher.on('reload_js', function(filename) { | |||||
filename = "assets/" + filename; | |||||
var msg = $(` | |||||
<span>${filename} changed <a data-action="reload">Click to Reload</a></span> | |||||
`) | |||||
msg.find('a').click(frappe.ui.toolbar.clear_cache); | |||||
frappe.show_alert({ | |||||
indicator: 'orange', | |||||
message: msg | |||||
}, 5); | |||||
}); | |||||
// commenting as this kills a branch change | |||||
// frappe.socketio.file_watcher.on('reload_js', function(filename) { | |||||
// filename = "assets/" + filename; | |||||
// var msg = $(` | |||||
// <span>${filename} changed <a data-action="reload">Click to Reload</a></span> | |||||
// `) | |||||
// msg.find('a').click(frappe.ui.toolbar.clear_cache); | |||||
// frappe.show_alert({ | |||||
// indicator: 'orange', | |||||
// message: msg | |||||
// }, 5); | |||||
// }); | |||||
}, | }, | ||||
process_response: function(data, method) { | process_response: function(data, method) { | ||||
if(!data) { | if(!data) { | ||||
@@ -495,14 +495,4 @@ frappe.ui.Page = Class.extend({ | |||||
this.wrapper.trigger('view-change'); | this.wrapper.trigger('view-change'); | ||||
}, | }, | ||||
}); | |||||
frappe.ui.scroll = function(element, animate, additional_offset) { | |||||
var header_offset = $(".navbar").height() + $(".page-head").height(); | |||||
var top = $(element).offset().top - header_offset - cint(additional_offset); | |||||
if (animate) { | |||||
$("html, body").animate({ scrollTop: top }); | |||||
} else { | |||||
$(window).scrollTop(top); | |||||
} | |||||
} | |||||
}); |
@@ -50,24 +50,27 @@ frappe.search.utils = { | |||||
find(values, keywords, function(match) { | find(values, keywords, function(match) { | ||||
var out = { | var out = { | ||||
route: match[1] | route: match[1] | ||||
} | |||||
if(match[1][0]==='Form' && match[1][2]) { | |||||
if(match[1][1] !== match[1][2]) { | |||||
}; | |||||
if (match[1][0]==='Form') { | |||||
if (match[1].length > 2 && match[1][1] !== match[1][2]) { | |||||
out.label = __(match[1][1]) + " " + match[1][2].bold(); | out.label = __(match[1][1]) + " " + match[1][2].bold(); | ||||
out.value = __(match[1][1]) + " " + match[1][2]; | out.value = __(match[1][1]) + " " + match[1][2]; | ||||
} else { | } else { | ||||
out.label = __(match[1][1]).bold(); | out.label = __(match[1][1]).bold(); | ||||
out.value = __(match[1][1]); | out.value = __(match[1][1]); | ||||
} | } | ||||
} else if(in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], match[1][0])) { | |||||
} else if (in_list(['List', 'Report', 'Tree', 'modules', 'query-report'], match[1][0]) && (match[1].length > 1)) { | |||||
var type = match[1][0], label = type; | var type = match[1][0], label = type; | ||||
if(type==='modules') label = 'Module'; | if(type==='modules') label = 'Module'; | ||||
else if(type==='query-report') label = 'Report'; | else if(type==='query-report') label = 'Report'; | ||||
out.label = __(match[1][1]).bold() + " " + __(label); | out.label = __(match[1][1]).bold() + " " + __(label); | ||||
out.value = __(match[1][1]) + " " + __(label); | out.value = __(match[1][1]) + " " + __(label); | ||||
} else { | |||||
} else if (match[0]) { | |||||
out.label = match[0].bold(); | out.label = match[0].bold(); | ||||
out.value = match[0]; | out.value = match[0]; | ||||
} else { | |||||
// eslint-disable-next-line | |||||
console.log('Illegal match', match); | |||||
} | } | ||||
out.index = 80; | out.index = 80; | ||||
return out; | return out; | ||||
@@ -492,7 +492,8 @@ _f.Frm.prototype.make_new = function(doctype) { | |||||
} | } | ||||
}); | }); | ||||
frappe.set_route('Form', doctype, new_doc.name); | |||||
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); | |||||
// frappe.set_route('Form', doctype, new_doc.name); | |||||
}); | }); | ||||
} | } | ||||
} | } | ||||
@@ -225,7 +225,7 @@ _f.Frm.prototype.watch_model_updates = function() { | |||||
}; | }; | ||||
_f.Frm.prototype.setup_std_layout = function() { | _f.Frm.prototype.setup_std_layout = function() { | ||||
this.form_wrapper = $('<div></div>').appendTo(this.layout_main); | |||||
this.form_wrapper = $('<div></div>').appendTo(this.layout_main); | |||||
this.body = $('<div></div>').appendTo(this.form_wrapper); | this.body = $('<div></div>').appendTo(this.form_wrapper); | ||||
// only tray | // only tray | ||||
@@ -53,7 +53,7 @@ def clear_global_cache(): | |||||
frappe.model.meta.clear_cache() | frappe.model.meta.clear_cache() | ||||
frappe.cache().delete_value(["app_hooks", "installed_apps", | frappe.cache().delete_value(["app_hooks", "installed_apps", | ||||
"app_modules", "module_app", "notification_config", 'system_settings' | "app_modules", "module_app", "notification_config", 'system_settings' | ||||
'scheduler_events', 'time_zone', 'webhooks']) | |||||
'scheduler_events', 'time_zone', 'webhooks', 'active_domains', 'active_modules']) | |||||
frappe.setup_module_map() | frappe.setup_module_map() | ||||
@@ -1,15 +1,11 @@ | |||||
frappe.tests = { | frappe.tests = { | ||||
data: {}, | data: {}, | ||||
get_fixture_names: (doctype) => { | |||||
return Object.keys(frappe.test_data[doctype]); | |||||
}, | |||||
make: function(doctype, data) { | make: function(doctype, data) { | ||||
return frappe.run_serially([ | return frappe.run_serially([ | ||||
() => frappe.set_route('List', doctype), | () => frappe.set_route('List', doctype), | ||||
() => frappe.new_doc(doctype), | () => frappe.new_doc(doctype), | ||||
() => { | () => { | ||||
if (frappe.quick_entry) | |||||
{ | |||||
if (frappe.quick_entry) { | |||||
frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(); | frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(); | ||||
return frappe.timeout(1); | return frappe.timeout(1); | ||||
} | } | ||||
@@ -79,13 +75,13 @@ frappe.tests = { | |||||
}); | }); | ||||
return frappe.run_serially(grid_row_tasks); | return frappe.run_serially(grid_row_tasks); | ||||
}, | }, | ||||
setup_doctype: (doctype) => { | |||||
setup_doctype: (doctype, data) => { | |||||
return frappe.run_serially([ | return frappe.run_serially([ | ||||
() => frappe.set_route('List', doctype), | () => frappe.set_route('List', doctype), | ||||
() => frappe.timeout(1), | () => frappe.timeout(1), | ||||
() => { | () => { | ||||
frappe.tests.data[doctype] = []; | frappe.tests.data[doctype] = []; | ||||
let expected = frappe.tests.get_fixture_names(doctype); | |||||
let expected = Object.keys(data); | |||||
cur_list.data.forEach((d) => { | cur_list.data.forEach((d) => { | ||||
frappe.tests.data[doctype].push(d.name); | frappe.tests.data[doctype].push(d.name); | ||||
if(expected.indexOf(d.name) !== -1) { | if(expected.indexOf(d.name) !== -1) { | ||||
@@ -98,7 +94,7 @@ frappe.tests = { | |||||
expected.forEach(function(d) { | expected.forEach(function(d) { | ||||
if(d) { | if(d) { | ||||
tasks.push(() => frappe.tests.make(doctype, | tasks.push(() => frappe.tests.make(doctype, | ||||
frappe.test_data[doctype][d])); | |||||
data[d])); | |||||
} | } | ||||
}); | }); | ||||
@@ -84,16 +84,19 @@ class TestDriver(object): | |||||
time.sleep(0.2) | time.sleep(0.2) | ||||
def set_field(self, fieldname, text): | def set_field(self, fieldname, text): | ||||
elem = self.find(xpath='//input[@data-fieldname="{0}"]'.format(fieldname)) | |||||
elem[0].send_keys(text) | |||||
elem = self.wait_for(xpath='//input[@data-fieldname="{0}"]'.format(fieldname)) | |||||
time.sleep(0.2) | |||||
elem.send_keys(text) | |||||
def set_select(self, fieldname, text): | def set_select(self, fieldname, text): | ||||
elem = self.find(xpath='//select[@data-fieldname="{0}"]'.format(fieldname)) | |||||
elem[0].send_keys(text) | |||||
elem = self.wait_for(xpath='//select[@data-fieldname="{0}"]'.format(fieldname)) | |||||
time.sleep(0.2) | |||||
elem.send_keys(text) | |||||
def set_text_editor(self, fieldname, text): | def set_text_editor(self, fieldname, text): | ||||
elem = self.find(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) | |||||
elem[0].send_keys(text) | |||||
elem = self.wait_for(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname)) | |||||
time.sleep(0.2) | |||||
elem.send_keys(text) | |||||
def find(self, selector=None, everywhere=False, xpath=None): | def find(self, selector=None, everywhere=False, xpath=None): | ||||
if xpath: | if xpath: | ||||
@@ -164,7 +167,11 @@ class TestDriver(object): | |||||
self.wait_for(xpath='//div[@data-page-route="{0}"]'.format('/'.join(args)), timeout=4) | self.wait_for(xpath='//div[@data-page-route="{0}"]'.format('/'.join(args)), timeout=4) | ||||
def click(self, css_selector, xpath=None): | def click(self, css_selector, xpath=None): | ||||
self.wait_till_clickable(css_selector, xpath).click() | |||||
element = self.wait_till_clickable(css_selector, xpath) | |||||
self.scroll_to(css_selector) | |||||
time.sleep(0.5) | |||||
element.click() | |||||
return element | |||||
def click_primary_action(self): | def click_primary_action(self): | ||||
selector = ".page-actions .primary-action" | selector = ".page-actions .primary-action" | ||||
@@ -201,6 +208,7 @@ class TestDriver(object): | |||||
return self.get_wait().until(EC.element_to_be_clickable( | return self.get_wait().until(EC.element_to_be_clickable( | ||||
(by, selector))) | (by, selector))) | ||||
def execute_script(self, js): | def execute_script(self, js): | ||||
self.driver.execute_script(js) | self.driver.execute_script(js) | ||||