Merge branch 'develop' into password-strength-for-long-passwords
This commit is contained in:
commit
ad77a74df9
11 changed files with 181 additions and 86 deletions
|
|
@ -11,6 +11,7 @@ from frappe.model.utils import render_include
|
|||
from frappe.modules import get_module_path, load_doctype_module, scrub
|
||||
from frappe.translate import extract_messages_from_code, make_dict_from_messages
|
||||
from frappe.utils import get_html_format
|
||||
from frappe.utils.data import get_link_to_form
|
||||
|
||||
ASSET_KEYS = (
|
||||
"__js",
|
||||
|
|
@ -199,14 +200,19 @@ class FormMeta(Meta):
|
|||
# customizations are removed or some custom app is removed but hasn't cleaned
|
||||
# up after itself.
|
||||
frappe.clear_last_message()
|
||||
customize_form_link = f'<a href="/app/customize-form/?doc_type={self.name}">Customize Form</a>'
|
||||
frappe.throw(
|
||||
_(
|
||||
"Field {0} is referring to non-existing doctype {1}, please remove the field from {2} or add the required doctype."
|
||||
).format(frappe.bold(df.fieldname), frappe.bold(df.options), customize_form_link),
|
||||
title=_("Missing DocType"),
|
||||
|
||||
msg = _("Field {0} is referring to non-existing doctype {1}.").format(
|
||||
frappe.bold(df.fieldname), frappe.bold(df.options)
|
||||
)
|
||||
|
||||
if df.get("is_custom_field"):
|
||||
custom_field_link = get_link_to_form("Custom Field", df.name)
|
||||
msg += " " + _("Please delete the field from {2} or add the required doctype.").format(
|
||||
custom_field_link
|
||||
)
|
||||
|
||||
frappe.throw(msg, title=_("Missing DocType"))
|
||||
|
||||
def add_linked_document_type(self):
|
||||
for df in self.get("fields", {"fieldtype": "Link"}):
|
||||
if df.options:
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class Notification(Document):
|
|||
frappe.cache().hdel("notifications", self.document_type)
|
||||
|
||||
def on_update(self):
|
||||
frappe.cache().hdel("notifications", self.document_type)
|
||||
path = export_module_json(self, self.is_standard, self.module)
|
||||
if path:
|
||||
# js
|
||||
|
|
|
|||
|
|
@ -88,8 +88,7 @@
|
|||
"fieldtype": "Link",
|
||||
"label": "Default User Role",
|
||||
"mandatory_depends_on": "eval: doc.default_user_type == \"System User\"",
|
||||
"options": "Role",
|
||||
"reqd": 1
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"description": "Must be enclosed in '()' and include '{0}', which is a placeholder for the user/login name. i.e. (&(objectclass=user)(uid={0}))",
|
||||
|
|
@ -302,7 +301,7 @@
|
|||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-05 21:52:31.146035",
|
||||
"modified": "2023-01-24 11:20:06.049708",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "LDAP Settings",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
|||
|
||||
class LDAPSettings(Document):
|
||||
def validate(self):
|
||||
self.default_user_type = self.default_user_type or "System User"
|
||||
self.default_user_type = self.default_user_type or "Website User"
|
||||
|
||||
if not self.enabled:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ class LDAP_TestCase:
|
|||
"ldap_username_field",
|
||||
"ldap_first_name_field",
|
||||
"require_trusted_certificate",
|
||||
"default_role",
|
||||
] # fields that are required to have ldap functioning need to be mandatory
|
||||
|
||||
for mandatory_field in mandatory_fields:
|
||||
|
|
|
|||
|
|
@ -35,54 +35,60 @@ DOCTYPES_FOR_DOCTYPE = {"DocType", *TABLE_DOCTYPES_FOR_DOCTYPE.values()}
|
|||
|
||||
|
||||
def get_controller(doctype):
|
||||
"""Returns the **class** object of the given DocType.
|
||||
"""
|
||||
Returns the locally cached **class** object of the given DocType.
|
||||
For `custom` type, returns `frappe.model.document.Document`.
|
||||
|
||||
:param doctype: DocType name as string."""
|
||||
|
||||
def _get_controller():
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
module_name, custom = frappe.db.get_value(
|
||||
"DocType", doctype, ("module", "custom"), cache=True
|
||||
) or ("Core", False)
|
||||
|
||||
if custom:
|
||||
is_tree = frappe.db.get_value("DocType", doctype, "is_tree", ignore=True, cache=True)
|
||||
_class = NestedSet if is_tree else Document
|
||||
else:
|
||||
class_overrides = frappe.get_hooks("override_doctype_class")
|
||||
if class_overrides and class_overrides.get(doctype):
|
||||
import_path = class_overrides[doctype][-1]
|
||||
module_path, classname = import_path.rsplit(".", 1)
|
||||
module = frappe.get_module(module_path)
|
||||
if not hasattr(module, classname):
|
||||
raise ImportError(f"{doctype}: {classname} does not exist in module {module_path}")
|
||||
else:
|
||||
module = load_doctype_module(doctype, module_name)
|
||||
classname = doctype.replace(" ", "").replace("-", "")
|
||||
|
||||
if hasattr(module, classname):
|
||||
_class = getattr(module, classname)
|
||||
if issubclass(_class, BaseDocument):
|
||||
_class = getattr(module, classname)
|
||||
else:
|
||||
raise ImportError(doctype)
|
||||
else:
|
||||
raise ImportError(doctype)
|
||||
return _class
|
||||
:param doctype: DocType name as string.
|
||||
"""
|
||||
|
||||
if frappe.local.dev_server:
|
||||
return _get_controller()
|
||||
return import_controller(doctype)
|
||||
|
||||
site_controllers = frappe.controllers.setdefault(frappe.local.site, {})
|
||||
if doctype not in site_controllers:
|
||||
site_controllers[doctype] = _get_controller()
|
||||
site_controllers[doctype] = import_controller(doctype)
|
||||
|
||||
return site_controllers[doctype]
|
||||
|
||||
|
||||
def import_controller(doctype):
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
module_name = "Core"
|
||||
if doctype not in DOCTYPES_FOR_DOCTYPE:
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.custom:
|
||||
return NestedSet if meta.get("is_tree") else Document
|
||||
|
||||
module_name = meta.module
|
||||
|
||||
module_path = None
|
||||
class_overrides = frappe.get_hooks("override_doctype_class")
|
||||
if class_overrides and class_overrides.get(doctype):
|
||||
import_path = class_overrides[doctype][-1]
|
||||
module_path, classname = import_path.rsplit(".", 1)
|
||||
module = frappe.get_module(module_path)
|
||||
|
||||
else:
|
||||
module = load_doctype_module(doctype, module_name)
|
||||
classname = doctype.replace(" ", "").replace("-", "")
|
||||
|
||||
class_ = getattr(module, classname, None)
|
||||
if class_ is None:
|
||||
raise ImportError(
|
||||
doctype
|
||||
if module_path is None
|
||||
else f"{doctype}: {classname} does not exist in module {module_path}"
|
||||
)
|
||||
|
||||
if not issubclass(class_, BaseDocument):
|
||||
raise ImportError(f"{doctype}: {classname} is not a subclass of BaseDocument")
|
||||
|
||||
return class_
|
||||
|
||||
|
||||
class BaseDocument:
|
||||
_reserved_keywords = {
|
||||
"doctype",
|
||||
|
|
|
|||
|
|
@ -7,22 +7,49 @@ frappe.ui.form.ControlPassword = class ControlPassword extends frappe.ui.form.Co
|
|||
make_input() {
|
||||
var me = this;
|
||||
super.make_input();
|
||||
this.$input
|
||||
.parent()
|
||||
.append($('<span class="password-strength-indicator indicator"></span>'));
|
||||
this.$wrapper
|
||||
.find(".control-input-wrapper")
|
||||
.append($('<p class="password-strength-message text-muted small hidden"></p>'));
|
||||
|
||||
this.indicator = this.$wrapper.find(".password-strength-indicator");
|
||||
this.indicator = $(
|
||||
`<div class="password-strength-indicator hidden">
|
||||
<div class="progress-text"></div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0" aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
).insertAfter(this.$input);
|
||||
|
||||
this.progress_text = this.indicator.find(".progress-text");
|
||||
this.progress_bar = this.indicator.find(".progress-bar");
|
||||
this.message = this.$wrapper.find(".help-box");
|
||||
|
||||
this.$input.on("keyup", () => {
|
||||
clearTimeout(this.check_password_timeout);
|
||||
this.check_password_timeout = setTimeout(() => {
|
||||
this.$input.on(
|
||||
"keyup",
|
||||
frappe.utils.debounce(() => {
|
||||
let hide_icon = me.$input.val() && !me.$input.val().includes("*");
|
||||
me.toggle_password.toggleClass("hidden", !hide_icon);
|
||||
me.get_password_strength(me.$input.val());
|
||||
}, 500);
|
||||
}, 500)
|
||||
);
|
||||
|
||||
this.toggle_password = $(`
|
||||
<div class="toggle-password hidden">
|
||||
${frappe.utils.icon("unhide", "sm")}
|
||||
</div>
|
||||
`).insertAfter(this.$input);
|
||||
|
||||
this.toggle_password.on("click", () => {
|
||||
if (this.$input.attr("type") === "password") {
|
||||
this.$input.attr("type", "text");
|
||||
this.toggle_password.html(frappe.utils.icon("hide", "sm"));
|
||||
} else {
|
||||
this.$input.attr("type", "password");
|
||||
this.toggle_password.html(frappe.utils.icon("unhide", "sm"));
|
||||
}
|
||||
});
|
||||
|
||||
!this.value && this.toggle_password.removeClass("hidden");
|
||||
}
|
||||
|
||||
disable_password_checks() {
|
||||
|
|
@ -33,6 +60,13 @@ frappe.ui.form.ControlPassword = class ControlPassword extends frappe.ui.form.Co
|
|||
if (!this.enable_password_checks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
this.indicator.addClass("hidden");
|
||||
this.message.addClass("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
var me = this;
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
|
|
@ -43,15 +77,34 @@ frappe.ui.form.ControlPassword = class ControlPassword extends frappe.ui.form.Co
|
|||
callback: function (r) {
|
||||
if (r.message) {
|
||||
let score = r.message.score;
|
||||
var indicators = ["red", "red", "orange", "yellow", "green"];
|
||||
var indicators = ["red", "red", "orange", "blue", "green"];
|
||||
me.set_strength_indicator(indicators[score]);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
set_strength_indicator(color) {
|
||||
var message = __("Include symbols, numbers and capital letters in the password");
|
||||
this.indicator.removeClass().addClass("password-strength-indicator indicator " + color);
|
||||
let strength = {
|
||||
red: [__("Weak"), "danger", 25],
|
||||
orange: [__("Average"), "warning", 50],
|
||||
blue: [__("Strong"), "info", 75],
|
||||
green: [__("Excellent"), "success", 100],
|
||||
};
|
||||
let progress_text = strength[color][0];
|
||||
let progress_color = strength[color][1];
|
||||
let progress_percent = strength[color][2];
|
||||
|
||||
this.indicator.removeClass("hidden");
|
||||
|
||||
this.progress_text.html(progress_text).css("color", `var(--${color}-500)`);
|
||||
|
||||
this.progress_bar
|
||||
.css("width", progress_percent + "%")
|
||||
.attr("aria-valuenow", progress_percent)
|
||||
.removeClass()
|
||||
.addClass("progress-bar progress-bar-" + progress_color);
|
||||
|
||||
let message = __("Include symbols, numbers and capital letters in the password");
|
||||
this.message.html(message).toggleClass("hidden", color == "green");
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -195,7 +195,9 @@ $.extend(frappe.perm, {
|
|||
}
|
||||
|
||||
if (!perm) {
|
||||
return df && (cint(df.hidden) || cint(df.hidden_due_to_dependency)) ? "None" : "Write";
|
||||
let is_hidden = df && (cint(df.hidden) || cint(df.hidden_due_to_dependency));
|
||||
let is_read_only = df && cint(df.read_only);
|
||||
return is_hidden ? "None" : is_read_only ? "Read" : "Write";
|
||||
}
|
||||
|
||||
if (!df.permlevel) df.permlevel = 0;
|
||||
|
|
|
|||
|
|
@ -5,20 +5,43 @@
|
|||
@import "phone_picker";
|
||||
|
||||
// password
|
||||
.form-control[data-fieldtype="Password"] {
|
||||
position: inherit;
|
||||
}
|
||||
.frappe-control[data-fieldtype="Password"] {
|
||||
.control-input-wrapper {
|
||||
position: relative;
|
||||
|
||||
.password-strength-indicator {
|
||||
// TODO: Review
|
||||
float: right;
|
||||
padding: 15px;
|
||||
margin-top: -41px;
|
||||
margin-right: -7px;
|
||||
}
|
||||
.form-control[data-fieldtype="Password"] {
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
.password-strength-message {
|
||||
margin-top: -10px;
|
||||
.password-strength-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
gap: 5px;
|
||||
top: -20px;
|
||||
right: 0px;
|
||||
|
||||
.progress-text {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: var(--bg-light-gray);
|
||||
width: 100px;
|
||||
height: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-password {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
padding: 3px;
|
||||
z-index: 3;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select
|
||||
|
|
@ -232,6 +255,10 @@ a.progress-small {
|
|||
background-color: var(--red-500);
|
||||
}
|
||||
|
||||
.progress-bar-info {
|
||||
background-color: var(--blue-500);
|
||||
}
|
||||
|
||||
.progress-bar-warning {
|
||||
background-color: var(--orange-500);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,16 +43,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
// hide row index in 6 column child tables
|
||||
.form-column.col-sm-6 .form-grid {
|
||||
.row-index {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-open-row {
|
||||
.edit-grid-row {
|
||||
// hide row index in 6/4 column child tables
|
||||
.form-column.col-sm-6, .form-column.col-sm-4 {
|
||||
.form-grid {
|
||||
.row-index {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn-open-row {
|
||||
.edit-grid-row {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -742,9 +742,9 @@ cookie@^0.4.0, cookie@~0.4.1:
|
|||
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
|
||||
|
||||
cookiejar@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
|
||||
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
|
||||
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
|
||||
|
||||
copy-anything@^2.0.1:
|
||||
version "2.0.3"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue