Преглед изворни кода

Merge branch 'develop' into icon-picker

version-14
Suraj Shetty пре 4 година
committed by GitHub
родитељ
комит
67a680f9f1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
18 измењених фајлова са 975 додато и 375 уклоњено
  1. +0
    -1
      frappe/auth.py
  2. +40
    -15
      frappe/commands/site.py
  3. +89
    -1
      frappe/core/doctype/doctype/doctype.js
  4. +3
    -0
      frappe/core/doctype/doctype/doctype.py
  5. +14
    -26
      frappe/integrations/utils.py
  6. +2
    -0
      frappe/public/js/frappe/form/controls/select.js
  7. +1
    -1
      frappe/public/js/frappe/form/form_tour.js
  8. +9
    -8
      frappe/public/js/frappe/form/grid.js
  9. +8
    -0
      frappe/public/js/frappe/form/layout.js
  10. +9
    -6
      frappe/public/js/frappe/list/list_view.js
  11. +10
    -1
      frappe/public/scss/desk/list.scss
  12. +5
    -2
      frappe/search/website_search.py
  13. +13
    -0
      frappe/tests/test_commands.py
  14. +37
    -2
      frappe/tests/test_translate.py
  15. +7
    -2
      frappe/translate.py
  16. +0
    -1
      frappe/utils/background_jobs.py
  17. +1
    -1
      package.json
  18. +727
    -308
      yarn.lock

+ 0
- 1
frappe/auth.py Прегледај датотеку

@@ -154,7 +154,6 @@ class LoginManager:
self.make_session()
self.setup_boot_cache()
self.set_user_info()
self.clear_preferred_language()

def get_user_info(self):
self.info = frappe.db.get_value("User", self.user,


+ 40
- 15
frappe/commands/site.py Прегледај датотеку

@@ -561,30 +561,54 @@ def move(dest_dir, site):
return final_new_path


@click.command('set-password')
@click.argument('user')
@click.argument('password', required=False)
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False)
@pass_context
def set_password(context, user, password=None, logout_all_sessions=False):
"Set password for a user on a site"
if not context.sites:
raise SiteNotSpecifiedError

for site in context.sites:
set_user_password(site, user, password, logout_all_sessions)


@click.command('set-admin-password')
@click.argument('admin-password')
@click.argument('admin-password', required=False)
@click.option('--logout-all-sessions', help='Logout from all sessions', is_flag=True, default=False)
@pass_context
def set_admin_password(context, admin_password, logout_all_sessions=False):
def set_admin_password(context, admin_password=None, logout_all_sessions=False):
"Set Administrator password for a site"
if not context.sites:
raise SiteNotSpecifiedError

for site in context.sites:
set_user_password(site, "Administrator", admin_password, logout_all_sessions)


def set_user_password(site, user, password, logout_all_sessions=False):
import getpass
from frappe.utils.password import update_password

for site in context.sites:
try:
frappe.init(site=site)
try:
frappe.init(site=site)

while not admin_password:
admin_password = getpass.getpass("Administrator's password for {0}: ".format(site))
while not password:
password = getpass.getpass(f"{user}'s password for {site}: ")

frappe.connect()
if not frappe.db.exists("User", user):
print(f"User {user} does not exist")
sys.exit(1)

update_password(user=user, pwd=password, logout_all_sessions=logout_all_sessions)
frappe.db.commit()
password = None
finally:
frappe.destroy()

frappe.connect()
update_password(user='Administrator', pwd=admin_password, logout_all_sessions=logout_all_sessions)
frappe.db.commit()
admin_password = None
finally:
frappe.destroy()
if not context.sites:
raise SiteNotSpecifiedError

@click.command('set-last-active-for-user')
@click.option('--user', help="Setup last active date for user")
@@ -729,6 +753,7 @@ commands = [
remove_from_installed_apps,
restore,
run_patch,
set_password,
set_admin_password,
uninstall,
disable_user,


+ 89
- 1
frappe/core/doctype/doctype/doctype.js Прегледај датотеку

@@ -66,4 +66,92 @@ frappe.ui.form.on('DocType', {
autoname: function(frm) {
frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt');
}
})
});

frappe.ui.form.on("DocField", {
form_render(frm, doctype, docname) {
// Render two select fields for Fetch From instead of Small Text for better UX
let field = frm.cur_grid.grid_form.fields_dict.fetch_from;
$(field.input_area).hide();

let $doctype_select = $(`<select class="form-control">`);
let $field_select = $(`<select class="form-control">`);
let $wrapper = $('<div class="fetch-from-select row"><div>');
$wrapper.append($doctype_select, $field_select);
field.$input_wrapper.append($wrapper);
$doctype_select.wrap('<div class="col"></div>');
$field_select.wrap('<div class="col"></div>');

let row = frappe.get_doc(doctype, docname);
let curr_value = { doctype: null, fieldname: null };
if (row.fetch_from) {
let [doctype, fieldname] = row.fetch_from.split(".");
curr_value.doctype = doctype;
curr_value.fieldname = fieldname;
}
let curr_df_link_doctype = row.fieldtype == "Link" ? row.options : null;

let doctypes = frm.doc.fields
.filter(df => df.fieldtype == "Link")
.filter(df => df.options && df.options != curr_df_link_doctype)
.map(df => ({
label: `${df.options} (${df.fieldname})`,
value: df.fieldname
}));
$doctype_select.add_options([
{ label: __("Select DocType"), value: "", selected: true },
...doctypes
]);

$doctype_select.on("change", () => {
row.fetch_from = "";
frm.dirty();
update_fieldname_options();
});

function update_fieldname_options() {
$field_select.find("option").remove();

let link_fieldname = $doctype_select.val();
if (!link_fieldname) return;
let link_field = frm.doc.fields.find(
df => df.fieldname === link_fieldname
);
let link_doctype = link_field.options;
frappe.model.with_doctype(link_doctype, () => {
let fields = frappe.meta
.get_docfields(link_doctype, null, {
fieldtype: ["not in", frappe.model.no_value_type]
})
.map(df => ({
label: `${df.label} (${df.fieldtype})`,
value: df.fieldname
}));
$field_select.add_options([
{
label: __("Select Field"),
value: "",
selected: true,
disabled: true
},
...fields
]);

if (curr_value.fieldname) {
$field_select.val(curr_value.fieldname);
}
});
}

$field_select.on("change", () => {
let fetch_from = `${$doctype_select.val()}.${$field_select.val()}`;
row.fetch_from = fetch_from;
frm.dirty();
});

if (curr_value.doctype) {
$doctype_select.val(curr_value.doctype);
update_fieldname_options();
}
}
});

+ 3
- 0
frappe/core/doctype/doctype/doctype.py Прегледај датотеку

@@ -931,6 +931,9 @@ def validate_fields(meta):
if meta.website_search_field not in fieldname_list:
frappe.throw(_("Website Search Field must be a valid fieldname"), InvalidFieldNameError)

if "title" not in fieldname_list:
frappe.throw(_('Field "title" is mandatory if "Website Search Field" is set.'), title=_("Missing Field"))

def check_timeline_field(meta):
if not meta.timeline_field:
return


+ 14
- 26
frappe/integrations/utils.py Прегледај датотеку

@@ -8,35 +8,14 @@ from urllib.parse import parse_qs
from frappe.utils import get_request_session
from frappe import _

def make_get_request(url, auth=None, headers=None, data=None):
if not auth:
auth = ''
if not data:
data = {}
if not headers:
headers = {}
def make_request(method, url, auth=None, headers=None, data=None):
auth = auth or ''
data = data or {}
headers = headers or {}

try:
s = get_request_session()
frappe.flags.integration_request = s.get(url, data={}, auth=auth, headers=headers)
frappe.flags.integration_request.raise_for_status()
return frappe.flags.integration_request.json()

except Exception as exc:
frappe.log_error(frappe.get_traceback())
raise exc

def make_post_request(url, auth=None, headers=None, data=None):
if not auth:
auth = ''
if not data:
data = {}
if not headers:
headers = {}

try:
s = get_request_session()
frappe.flags.integration_request = s.post(url, data=data, auth=auth, headers=headers)
frappe.flags.integration_request = s.request(method, url, data=data, auth=auth, headers=headers)
frappe.flags.integration_request.raise_for_status()

if frappe.flags.integration_request.headers.get("content-type") == "text/plain; charset=utf-8":
@@ -47,6 +26,15 @@ def make_post_request(url, auth=None, headers=None, data=None):
frappe.log_error()
raise exc

def make_get_request(url, **kwargs):
return make_request('GET', url, **kwargs)

def make_post_request(url, **kwargs):
return make_request('POST', url, **kwargs)

def make_put_request(url, **kwargs):
return make_request('PUT', url, **kwargs)

def create_request_log(data, integration_type, service_name, name=None, error=None):
if isinstance(data, str):
data = json.loads(data)


+ 2
- 0
frappe/public/js/frappe/form/controls/select.js Прегледај датотеку

@@ -113,6 +113,7 @@ frappe.ui.form.ControlSelect = class ControlSelect extends frappe.ui.form.Contro
var is_value_null = is_null(v.value);
var is_label_null = is_null(v.label);
var is_disabled = Boolean(v.disabled);
var is_selected = Boolean(v.selected);

if (is_value_null && is_label_null) {
value = v;
@@ -126,6 +127,7 @@ frappe.ui.form.ControlSelect = class ControlSelect extends frappe.ui.form.Contro
$('<option>').html(cstr(label))
.attr('value', value)
.prop('disabled', is_disabled)
.prop('selected', is_selected)
.appendTo(this);
}
// select the first option


+ 1
- 1
frappe/public/js/frappe/form/form_tour.js Прегледај датотеку

@@ -92,7 +92,7 @@ frappe.ui.form.FormTour = class FormTour {
return {
element,
name,
popover: { title, description, position: frappe.router.slug(position) },
popover: { title, description, position: frappe.router.slug(position || 'Bottom') },
onNext: on_next
};
}


+ 9
- 8
frappe/public/js/frappe/form/grid.js Прегледај датотеку

@@ -264,15 +264,16 @@ export default class Grid {

make_head() {
// labels
if (!this.header_row) {
this.header_row = new GridRow({
parent: $(this.parent).find(".grid-heading-row"),
parent_df: this.df,
docfields: this.docfields,
frm: this.frm,
grid: this
});
if (this.header_row) {
$(this.parent).find(".grid-heading-row .grid-row").remove();
}
this.header_row = new GridRow({
parent: $(this.parent).find(".grid-heading-row"),
parent_df: this.df,
docfields: this.docfields,
frm: this.frm,
grid: this
});
}

refresh(force) {


+ 8
- 0
frappe/public/js/frappe/form/layout.js Прегледај датотеку

@@ -250,6 +250,14 @@ frappe.ui.form.Layout = class Layout {
// collapse sections
this.refresh_section_collapse();
}

if (document.activeElement) {
document.activeElement.focus();
if (document.activeElement.tagName == 'INPUT') {
document.activeElement.select();
}
}
}

refresh_sections() {


+ 9
- 6
frappe/public/js/frappe/list/list_view.js Прегледај датотеку

@@ -514,7 +514,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {

render_skeleton() {
const $row = this.get_list_row_html_skeleton(
'<div><input type="checkbox" /></div>'
'<div><input type="checkbox" class="render-list-checkbox"/></div>'
);
this.$result.append($row);
}
@@ -927,10 +927,12 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const seen = this.get_seen_class(doc);

let subject_html = `
<input class="level-item list-row-checkbox hidden-xs" type="checkbox"
data-name="${escape(doc.name)}">
<span class="level-item" style="margin-bottom: 1px;">
${this.get_like_html(doc)}
<span class="level-item select-like">
<input class="list-row-checkbox hidden-xs" type="checkbox"
data-name="${escape(doc.name)}">
<span class="list-row-like style="margin-bottom: 1px;">
${this.get_like_html(doc)}
</span>
</span>
<span class="level-item ${seen} ellipsis" title="${escaped_subject}">
<a class="ellipsis"
@@ -1127,7 +1129,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
// don't open form when checkbox, like, filterable are clicked
if (
$target.hasClass("filterable") ||
$target.hasClass("icon-heart") ||
$target.hasClass("select-like") ||
$target.hasClass("list-row-like") ||
$target.is(":checkbox")
) {
e.stopPropagation();


+ 10
- 1
frappe/public/scss/desk/list.scss Прегледај датотеку

@@ -58,7 +58,7 @@
}

.list-row {
padding: 15px;
padding: 15px 15px 15px 0px;
height: 45px;
cursor: pointer;
transition: color 0.2s;
@@ -130,10 +130,15 @@
margin-left: 5px;
}
}

.select-like {
padding: 15px 0px 15px 15px;
}
}

.list-row-head {
@extend .list-row;
padding: 15px;
cursor: default;

.list-subject {
@@ -200,6 +205,10 @@ input.list-check-all, input.list-row-checkbox {
--checkbox-right-margin: calc(var(--checkbox-size) / 2 + #{$level-margin-right});
}

.render-list-checkbox {
margin-left: 15px;
}

.filterable {
cursor: pointer;
}


+ 5
- 2
frappe/search/website_search.py Прегледај датотеку

@@ -90,19 +90,22 @@ class WebsiteSearch(FullTextSearch):
def slugs_with_web_view(_items_to_index):
all_routes = []
filters = { "has_web_view": 1, "allow_guest_to_view": 1, "index_web_pages_for_search": 1}
fields = ["name", "is_published_field", 'website_search_field']
fields = ["name", "is_published_field", "website_search_field"]
doctype_with_web_views = frappe.get_all("DocType", filters=filters, fields=fields)

for doctype in doctype_with_web_views:
if doctype.is_published_field:
docs = frappe.get_all(doctype.name, filters={doctype.is_published_field: 1}, fields=["route", doctype.website_search_field, 'title'])
fields=["route", doctype.website_search_field]
filters={doctype.is_published_field: 1},
if doctype.website_search_field:
docs = frappe.get_all(doctype.name, filters=filters, fields=fields.append("title"))
for doc in docs:
content = frappe.utils.md_to_html(getattr(doc, doctype.website_search_field))
soup = BeautifulSoup(content, "html.parser")
text_content = soup.text if soup else ""
_items_to_index += [frappe._dict(title=doc.title, content=text_content, path=doc.route)]
else:
docs = frappe.get_all(doctype.name, filters=filters, fields=fields)
all_routes += [route.route for route in docs]

return all_routes


+ 13
- 0
frappe/tests/test_commands.py Прегледај датотеку

@@ -436,3 +436,16 @@ class TestCommands(BaseTestCommands):

self.execute("bench version -f invalid")
self.assertEqual(self.returncode, 2)

def test_set_password(self):
from frappe.utils.password import check_password

self.execute("bench --site {site} set-password Administrator test1")
self.assertEqual(self.returncode, 0)
self.assertEqual(check_password('Administrator', 'test1'), 'Administrator')
# to release the lock taken by check_password
frappe.db.commit()

self.execute("bench --site {site} set-admin-password test2")
self.assertEqual(self.returncode, 0)
self.assertEqual(check_password('Administrator', 'test2'), 'Administrator')

+ 37
- 2
frappe/tests/test_translate.py Прегледај датотеку

@@ -18,8 +18,19 @@ first_lang, second_lang, third_lang, fourth_lang, fifth_lang = choices(
)

class TestTranslate(unittest.TestCase):
guest_sessions_required = [
"test_guest_request_language_resolution_with_cookie",
"test_guest_request_language_resolution_with_request_header"
]

def setUp(self):
if self._testMethodName in self.guest_sessions_required:
frappe.set_user("Guest")

def tearDown(self):
frappe.form_dict.pop("_lang", None)
if self._testMethodName in self.guest_sessions_required:
frappe.set_user("Administrator")

def test_extract_message_from_file(self):
data = frappe.translate.get_messages_from_file(translation_string_file)
@@ -52,21 +63,45 @@ class TestTranslate(unittest.TestCase):
Case 2: frappe.form_dict._lang is not set, but preferred_language cookie is
"""

with patch.object(frappe.translate, "get_preferred_language_cookie", return_value=second_lang):
set_request(method="POST", path="/", headers=[("Accept-Language", third_lang)])
return_val = get_language()

self.assertNotIn(return_val, [second_lang, get_parent_language(second_lang)])

def test_guest_request_language_resolution_with_cookie(self):
"""Test for frappe.translate.get_language

Case 3: frappe.form_dict._lang is not set, but preferred_language cookie is [Guest User]
"""

with patch.object(frappe.translate, "get_preferred_language_cookie", return_value=second_lang):
set_request(method="POST", path="/", headers=[("Accept-Language", third_lang)])
return_val = get_language()

self.assertIn(return_val, [second_lang, get_parent_language(second_lang)])

def test_request_language_resolution_with_request_header(self):

def test_guest_request_language_resolution_with_request_header(self):
"""Test for frappe.translate.get_language

Case 3: frappe.form_dict._lang & preferred_language cookie is not set, but Accept-Language header is
Case 4: frappe.form_dict._lang & preferred_language cookie is not set, but Accept-Language header is [Guest User]
"""

set_request(method="POST", path="/", headers=[("Accept-Language", third_lang)])
return_val = get_language()
self.assertIn(return_val, [third_lang, get_parent_language(third_lang)])

def test_request_language_resolution_with_request_header(self):
"""Test for frappe.translate.get_language

Case 5: frappe.form_dict._lang & preferred_language cookie is not set, but Accept-Language header is
"""

set_request(method="POST", path="/", headers=[("Accept-Language", third_lang)])
return_val = get_language()
self.assertNotIn(return_val, [third_lang, get_parent_language(third_lang)])


expected_output = [
('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', 'This is some context', 2),


+ 7
- 2
frappe/translate.py Прегледај датотеку

@@ -27,11 +27,12 @@ def get_language(lang_list: List = None) -> str:

Order of priority for setting language:
1. Form Dict => _lang
2. Cookie => preferred_language
3. Request Header => Accept-Language
2. Cookie => preferred_language (Non authorized user)
3. Request Header => Accept-Language (Non authorized user)
4. User document => language
5. System Settings => language
"""
is_logged_in = frappe.session.user != "Guest"

# fetch language from form_dict
if frappe.form_dict._lang:
@@ -41,6 +42,10 @@ def get_language(lang_list: List = None) -> str:
if language:
return language

# use language set in User or System Settings if user is logged in
if is_logged_in:
return frappe.local.lang

lang_set = set(lang_list or get_all_languages() or [])

# fetch language from cookie


+ 0
- 1
frappe/utils/background_jobs.py Прегледај датотеку

@@ -20,7 +20,6 @@ from frappe.utils.commands import log

default_timeout = 300
queue_timeout = {
'background': 2500,
'long': 1500,
'default': 300,
'short': 300


+ 1
- 1
package.json Прегледај датотеку

@@ -52,7 +52,7 @@
"qz-tray": "^2.0.8",
"redis": "^3.1.1",
"showdown": "^1.9.1",
"snyk": "^1.518.0",
"snyk": "^1.667.0",
"socket.io": "^2.4.0",
"superagent": "^3.8.2",
"touch": "^3.1.0",


+ 727
- 308
yarn.lock
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


Loading…
Откажи
Сачувај