Merge branch 'frappe:develop' into primary-navbar-css-fix

This commit is contained in:
Shariq Ansari 2021-10-25 11:23:41 +05:30 committed by GitHub
commit 8f0d79d283
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 245 additions and 219 deletions

View file

@ -7,12 +7,13 @@ context('Awesome Bar', () => {
beforeEach(() => {
cy.get('.navbar .navbar-home').click();
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').clear();
});
it('navigates to doctype list', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 200 });
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 700 });
cy.get('.awesomplete').findByRole('listbox').should('be.visible');
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{downarrow}{enter}', { delay: 100 });
cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{downarrow}{enter}', { delay: 700 });
cy.get('.title-text').should('contain', 'To Do');
@ -21,7 +22,7 @@ context('Awesome Bar', () => {
it('find text in doctype list', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
.type('test in todo{downarrow}{enter}', { delay: 200 });
.type('test in todo{downarrow}{enter}', { delay: 700 });
cy.get('.title-text').should('contain', 'To Do');
@ -31,14 +32,14 @@ context('Awesome Bar', () => {
it('navigates to new form', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
.type('new blog post{downarrow}{enter}', { delay: 200 });
.type('new blog post{downarrow}{enter}', { delay: 700 });
cy.get('.title-text:visible').should('have.text', 'New Blog Post');
});
it('calculates math expressions', () => {
cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
.type('55 + 32{downarrow}{enter}', { delay: 200 });
.type('55 + 32{downarrow}{enter}', { delay: 700 });
cy.get('.modal-title').should('contain', 'Result');
cy.get('.msgprint').should('contain', '55 + 32 = 87');

View file

@ -49,19 +49,19 @@ context('Control Link', () => {
it('should unset invalid value', () => {
get_dialog_with_link().as('dialog');
cy.intercept('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.intercept('GET', '/api/method/frappe.client.get_value*').as('get_value');
cy.get('.frappe-control[data-fieldname=link] input')
.type('invalid value', { delay: 100 })
.blur();
cy.wait('@validate_link');
cy.wait('@get_value');
cy.get('.frappe-control[data-fieldname=link] input').should('have.value', '');
});
it('should route to form on arrow click', () => {
get_dialog_with_link().as('dialog');
cy.intercept('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.intercept('GET', '/api/method/frappe.client.get_value*').as('get_value');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.get('@todos').then(todos => {
@ -69,7 +69,7 @@ context('Control Link', () => {
cy.get('@input').focus();
cy.wait('@search_link');
cy.get('@input').type(todos[0]).blur();
cy.wait('@validate_link');
cy.wait('@get_value');
cy.get('@input').focus();
cy.findByTitle('Open Link')
.should('be.visible')
@ -77,4 +77,19 @@ context('Control Link', () => {
cy.location('pathname').should('eq', `/app/todo/${todos[0]}`);
});
});
it('should fetch valid value', () => {
cy.get('@todos').then(todos => {
cy.visit(`/app/todo/${todos[0]}`);
cy.intercept('GET', '/api/method/frappe.client.get_value*').as('get_value');
cy.get('.frappe-control[data-fieldname=assigned_by] input').focus().as('input');
cy.get('@input').type('Administrator', {delay: 100}).blur();
cy.wait('@get_value');
cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
'contain', 'Administrator'
);
});
});
});

View file

@ -13,13 +13,6 @@ context('Recorder', () => {
});
});
it('Navigate to Recorder', () => {
cy.visit('/app');
cy.awesomebar('recorder');
cy.findByTitle('Recorder').should('exist');
cy.url().should('include', '/recorder/detail');
});
it('Recorder Empty State', () => {
cy.findByTitle('Recorder').should('exist');

View file

@ -241,7 +241,7 @@ Cypress.Commands.add('get_table_field', (tablefieldname, row_idx, fieldname, fie
});
Cypress.Commands.add('awesomebar', text => {
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, {delay: 100});
cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, {delay: 700});
});
Cypress.Commands.add('new_form', doctype => {
@ -354,4 +354,4 @@ Cypress.Commands.add('click_listview_primary_button', (btn_name) => {
Cypress.Commands.add('click_timeline_action_btn', (btn_name) => {
cy.get('.timeline-message-box .custom-actions > .btn').contains(btn_name).click();
});
});

View file

@ -46,7 +46,7 @@ let argv = yargs
})
.option("live-reload", {
type: "boolean",
description: `Automatically reload web pages when assets are rebuilt.
description: `Automatically reload Desk when assets are rebuilt.
Can only be used with the --watch flag.`
})
.option("production", {

View file

@ -276,18 +276,17 @@ def bulk_update(docs):
docs = json.loads(docs)
failed_docs = []
for doc in docs:
doc.pop("flags", None)
try:
ddoc = {key: val for key, val in doc.items() if key not in ['doctype', 'docname']}
doctype = doc['doctype']
docname = doc['docname']
doc = frappe.get_doc(doctype, docname)
doc.update(ddoc)
doc.save()
except:
existing_doc = frappe.get_doc(doc["doctype"], doc["docname"])
existing_doc.update(doc)
existing_doc.save()
except Exception:
failed_docs.append({
'doc': doc,
'exc': frappe.utils.get_traceback()
})
return {'failed_docs': failed_docs}
@frappe.whitelist()

View file

@ -7,6 +7,9 @@ from frappe.utils import get_fullname, now
from frappe.model.document import Document
from frappe.core.utils import set_timeline_doc
import frappe
from frappe.query_builder import DocType, Interval
from frappe.query_builder.functions import Now
from pypika.terms import PseudoColumn
class ActivityLog(Document):
def before_insert(self):
@ -44,6 +47,7 @@ def clear_activity_logs(days=None):
if not days:
days = 90
frappe.db.sql("""delete from `tabActivity Log` where \
creation< (NOW() - INTERVAL '{0}' DAY)""".format(days))
doctype = DocType("Activity Log")
frappe.db.delete(doctype, filters=(
doctype.creation < PseudoColumn(f"({Now() - Interval(days=days)})")
))

View file

@ -49,7 +49,7 @@
"label": "Clear Activity Log After"
},
{
"default": "90",
"default": "30",
"description": "In Days",
"fieldname": "clear_email_queue_after",
"fieldtype": "Int",
@ -80,4 +80,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View file

@ -5,6 +5,10 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder import DocType, Interval
from frappe.query_builder.functions import Now
from pypika.terms import PseudoColumn
class LogSettings(Document):
def clear_logs(self):
@ -13,9 +17,10 @@ class LogSettings(Document):
self.clear_email_queue()
def clear_error_logs(self):
frappe.db.sql(""" DELETE FROM `tabError Log`
WHERE `creation` < (NOW() - INTERVAL '{0}' DAY)
""".format(self.clear_error_log_after))
table = DocType("Error Log")
frappe.db.delete(table, filters=(
table.creation < PseudoColumn(f"({Now() - Interval(days=self.clear_error_log_after)})")
))
def clear_activity_logs(self):
from frappe.core.doctype.activity_log.activity_log import clear_activity_logs

View file

@ -105,7 +105,7 @@ class Report(Document):
if not self.query.lower().startswith("select"):
frappe.throw(_("Query must be a SELECT"), title=_('Report Document Error'))
result = [list(t) for t in frappe.db.sql(self.query, filters, debug=True)]
result = [list(t) for t in frappe.db.sql(self.query, filters)]
columns = self.get_columns() or [cstr(c[0]) for c in frappe.db.get_description()]
return [columns, result]

View file

@ -16,6 +16,7 @@ from frappe.utils.user import get_system_managers
from frappe.website.utils import is_signup_disabled
from frappe.rate_limiter import rate_limit
from frappe.core.doctype.user_type.user_type import user_linked_with_permission_on_doctype
from frappe.query_builder import DocType
STANDARD_USERS = ("Guest", "Administrator")
@ -366,15 +367,21 @@ class User(Document):
# delete shares
frappe.db.delete("DocShare", {"user": self.name})
# delete messages
frappe.db.sql("""delete from `tabCommunication`
where communication_type in ('Chat', 'Notification')
and reference_doctype='User'
and (reference_name=%s or owner=%s)""", (self.name, self.name))
table = DocType("Communication")
frappe.db.delete(
table,
filters=(
(table.communication_type.isin(["Chat", "Notification"]))
& (table.reference_doctype == "User")
& ((table.reference_name == self.name) | table.owner == self.name)
),
run=False,
)
# unlink contact
frappe.db.sql("""update `tabContact`
set `user`=null
where `user`=%s""", (self.name))
table = DocType("Contact")
frappe.qb.update(table).where(
table.user == self.name
).set(table.user, None).run()
# delete notification settings
frappe.delete_doc("Notification Settings", self.name, ignore_permissions=True)
@ -421,9 +428,10 @@ class User(Document):
frappe.rename_doc("Notification Settings", old_name, new_name, force=True, show_alert=False)
# set email
frappe.db.sql("""UPDATE `tabUser`
SET email = %s
WHERE name = %s""", (new_name, new_name))
table = DocType("User")
frappe.qb.update(table).where(
table.name == new_name
).set("email", new_name).run()
def append_roles(self, *roles):
"""Add roles to user"""

View file

@ -195,7 +195,7 @@ def get_user_linked_doctypes(doctype, txt, searchfield, start, page_len, filters
['DocType', 'read_only', '=', 0], ['DocType', 'name', 'like', '%{0}%'.format(txt)]]
doctypes = frappe.get_all('DocType', fields = ['`tabDocType`.`name`'], filters=filters,
order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1, debug=1)
order_by = '`tabDocType`.`idx` desc', limit_start=start, limit_page_length=page_len, as_list=1)
custom_dt_filters = [['Custom Field', 'dt', 'like', '%{0}%'.format(txt)],
['Custom Field', 'options', '=', 'User'], ['Custom Field', 'fieldtype', '=', 'Link']]

View file

@ -83,7 +83,8 @@ class Database(object):
pass
def sql(self, query, values=(), as_dict = 0, as_list = 0, formatted = 0,
debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None, explain=False, run=True):
debug=0, ignore_ddl=0, as_utf8=0, auto_commit=0, update=None,
explain=False, run=True, pluck=False):
"""Execute a SQL query and fetch all rows.
:param query: SQL query.
@ -178,6 +179,9 @@ class Database(object):
if not self._cursor.description:
return ()
if pluck:
return [r[0] for r in self._cursor.fetchall()]
# scrub output if required
if as_dict:
ret = self.fetch_as_dict(formatted, as_utf8)
@ -233,7 +237,7 @@ class Database(object):
except Exception:
frappe.errprint("error in query explain")
def sql_list(self, query, values=(), debug=False):
def sql_list(self, query, values=(), debug=False, **kwargs):
"""Return data as list of single elements (first column).
Example:
@ -241,7 +245,7 @@ class Database(object):
# doctypes = ["DocType", "DocField", "User", ...]
doctypes = frappe.db.sql_list("select name from DocType")
"""
return [r[0] for r in self.sql(query, values, debug=debug)]
return [r[0] for r in self.sql(query, values, **kwargs, debug=debug)]
def sql_ddl(self, query, values=(), debug=False):
"""Commit and execute a query. DDL (Data Definition Language) queries that alter schema

View file

@ -4,6 +4,7 @@
import frappe
from frappe.desk.notifications import clear_notifications
from frappe.cache_manager import clear_defaults_cache, common_default_keys
from frappe.query_builder import DocType
# Note: DefaultValue records are identified by parenttype
# __default, __global or 'User Permission'
@ -116,14 +117,11 @@ def set_default(key, value, parent, parenttype="__default"):
:param value: Default value.
:param parent: Usually, **User** to whom the default belongs.
:param parenttype: [optional] default is `__default`."""
if frappe.db.sql('''
select
defkey
from
`tabDefaultValue`
where
defkey=%s and parent=%s
for update''', (key, parent)):
table = DocType("DefaultValue")
key_exists = frappe.qb.from_(table).where(
(table.defkey == key) & (table.parent == parent)
).select(table.defkey).for_update().run()
if key_exists:
frappe.db.delete("DefaultValue", {
"defkey": key,
"parent": parent
@ -191,8 +189,12 @@ def get_defaults_for(parent="__default"):
if defaults==None:
# sort descending because first default must get precedence
res = frappe.db.sql("""select defkey, defvalue from `tabDefaultValue`
where parent = %s order by creation""", (parent,), as_dict=1)
table = DocType("DefaultValue")
res = frappe.qb.from_(table).where(
table.parent == parent
).select(
table.defkey, table.defvalue
).orderby("creation").run(as_dict=True)
defaults = frappe._dict({})
for d in res:

View file

@ -16,44 +16,6 @@ def remove_attach():
file_name = frappe.form_dict.get('file_name')
frappe.delete_doc('File', fid)
@frappe.whitelist()
def validate_link():
"""validate link when updated by user"""
import frappe
import frappe.utils
value, options, fetch = frappe.form_dict.get('value'), frappe.form_dict.get('options'), frappe.form_dict.get('fetch')
# no options, don't validate
if not options or options=='null' or options=='undefined':
frappe.response['message'] = 'Ok'
return
valid_value = frappe.db.get_all(options, filters=dict(name=value), as_list=1, limit=1)
if valid_value:
valid_value = valid_value[0][0]
# get fetch values
if fetch:
# escape with "`"
fetch = ", ".join(("`{0}`".format(f.strip()) for f in fetch.split(",")))
fetch_value = None
try:
fetch_value = frappe.db.sql("select %s from `tab%s` where name=%s"
% (fetch, options, '%s'), (value,))[0]
except Exception as e:
error_message = str(e).split("Unknown column '")
fieldname = None if len(error_message)<=1 else error_message[1].split("'")[0]
frappe.msgprint(_("Wrong fieldname <b>{0}</b> in add_fetch configuration of custom client script").format(fieldname))
frappe.errprint(frappe.get_traceback())
if fetch_value:
frappe.response['fetch_values'] = [frappe.utils.parse_val(c) for c in fetch_value]
frappe.response['valid_value'] = valid_value
frappe.response['message'] = 'Ok'
@frappe.whitelist()
def add_comment(reference_doctype, reference_name, content, comment_email, comment_by):

View file

@ -2,6 +2,7 @@
import frappe
from frappe.desk.form.linked_with import get_linked_doctypes
from frappe.patches.v11_0.replicate_old_user_permissions import get_doctypes_to_skip
from frappe.query_builder import Field
# `skip_for_doctype` was a un-normalized way of storing for which
# doctypes the user permission was applicable.
@ -72,16 +73,12 @@ def execute():
frappe.db.set_value('User Permission', user_permission.name, 'apply_to_all_doctypes', 1)
if new_user_permissions_list:
frappe.db.sql('''
INSERT INTO `tabUser Permission`
(`name`, `user`, `allow`, `for_value`, `applicable_for`, `apply_to_all_doctypes`, `creation`, `modified`)
VALUES {}
'''.format( # nosec
', '.join(['%s'] * len(new_user_permissions_list))
), tuple(new_user_permissions_list))
frappe.qb.into("User Permission").columns(
"name", "user", "allow", "for_value", "applicable_for", "apply_to_all_doctypes", "creation", "modified"
).insert(tuple(new_user_permissions_list)).run()
if user_permissions_to_delete:
frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `name` in ({})' # nosec
.format(','.join(['%s'] * len(user_permissions_to_delete))),
tuple(user_permissions_to_delete)
frappe.db.delete(
"User Permission",
filters=(Field("name").isin(tuple(user_permissions_to_delete)))
)

View file

@ -6,7 +6,7 @@ import frappe
import frappe.share
from frappe import _, msgprint
from frappe.utils import cint
from frappe.query_builder import DocType
rights = ("select", "read", "write", "create", "delete", "submit", "cancel", "amend",
"print", "email", "report", "import", "export", "set_user_permissions", "share")
@ -330,8 +330,7 @@ def get_all_perms(role):
'''Returns valid permissions for a given role'''
perms = frappe.get_all('DocPerm', fields='*', filters=dict(role=role))
custom_perms = frappe.get_all('Custom DocPerm', fields='*', filters=dict(role=role))
doctypes_with_custom_perms = frappe.db.sql_list("""select distinct parent
from `tabCustom DocPerm`""")
doctypes_with_custom_perms = frappe.get_all("Custom DocPerm", pluck="parent", distinct=True)
for p in perms:
if p.parent not in doctypes_with_custom_perms:
@ -348,10 +347,13 @@ def get_roles(user=None, with_standard=True):
def get():
if user == 'Administrator':
return [r[0] for r in frappe.db.sql("select name from `tabRole`")] # return all available roles
return frappe.get_all("Role", pluck="name") # return all available roles
else:
return [r[0] for r in frappe.db.sql("""select role from `tabHas Role`
where parent=%s and role not in ('All', 'Guest')""", (user,))] + ['All', 'Guest']
table = DocType("Has Role")
roles = frappe.qb.from_(table).where(
(table.parent == user) & (table.role.notin(["All", "Guest"]))
).select(table.role).run(pluck=True)
return roles + ['All', 'Guest']
roles = frappe.cache().hget("roles", user, get)
@ -460,10 +462,9 @@ def update_permission_property(doctype, role, permlevel, ptype, value=None, vali
name = frappe.get_value('Custom DocPerm', dict(parent=doctype, role=role,
permlevel=permlevel))
table = DocType("Custom DocPerm")
frappe.qb.update(table).set(ptype, value).where(table.name == name).run()
frappe.db.sql("""
update `tabCustom DocPerm`
set `{0}`=%s where name=%s""".format(ptype), (value, name))
if validate:
validate_permissions_for_doctype(doctype)

View file

@ -451,51 +451,55 @@ frappe.ui.form.ControlLink = class ControlLink extends frappe.ui.form.ControlDat
return this.validate_link_and_fetch(this.df, this.get_options(),
this.docname, value);
}
validate_link_and_fetch(df, doctype, docname, value) {
if(value) {
return new Promise((resolve) => {
var fetch = '';
if(this.frm && this.frm.fetch_dict[df.fieldname]) {
fetch = this.frm.fetch_dict[df.fieldname].columns.join(', ');
}
// if default and no fetch, no need to validate
if (!fetch && df.__default_value && df.__default_value===value) {
resolve(value);
}
validate_link_and_fetch(df, options, docname, value) {
if (!value) return;
this.fetch_and_validate_link(resolve, df, doctype, docname, value, fetch);
});
}
}
return new Promise((resolve) => {
const fetch_map = this.fetch_map;
fetch_and_validate_link(resolve, df, doctype, docname, value, fetch) {
frappe.call({
method: 'frappe.desk.form.utils.validate_link',
type: "GET",
args: {
'value': value,
'options': doctype,
'fetch': fetch
},
no_spinner: true,
callback: (r) => {
if (r.message=='Ok') {
if (r.fetch_values && docname) {
this.set_fetch_values(df, docname, r.fetch_values);
}
resolve(r.valid_value);
} else {
resolve("");
}
// if default and no fetch, no need to validate
if ($.isEmptyObject(fetch_map) && df.__default_value === value) {
return resolve(value);
}
frappe.db.get_value(
options,
value,
["name", ...Object.values(fetch_map)],
(response) => {
if (!response.name) {
return resolve("");
}
if (docname) {
for (const [target_field, source_field] of Object.entries(fetch_map)) {
frappe.model.set_value(
df.parent,
docname,
target_field,
response[source_field],
df.fieldtype,
);
}
}
return resolve(response.name);
}
)
});
}
set_fetch_values(df, docname, fetch_values) {
var fl = this.frm.fetch_dict[df.fieldname].fields;
for(var i=0; i < fl.length; i++) {
frappe.model.set_value(df.parent, docname, fl[i], fetch_values[i], df.fieldtype);
get fetch_map() {
const fetch_map = {};
if (!this.frm) return fetch_map;
for (const key of ["*", this.df.parent]) {
if (this.frm.fetch_dict[key] && this.frm.fetch_dict[key][this.df.fieldname]) {
Object.assign(fetch_map, this.frm.fetch_dict[key][this.df.fieldname]);
}
}
return fetch_map;
}
};

View file

@ -1112,12 +1112,24 @@ frappe.ui.form.Form = class FrappeForm {
}
// UTILITIES
add_fetch(link_field, src_field, tar_field) {
if(!this.fetch_dict[link_field]) {
this.fetch_dict[link_field] = {'columns':[], 'fields':[]};
}
this.fetch_dict[link_field].columns.push(src_field);
this.fetch_dict[link_field].fields.push(tar_field);
add_fetch(link_field, source_field, target_field, target_doctype) {
/*
Example fetch dict to get sender_email from email_id field in sender:
{
"Notification": {
"sender": {
"sender_email": "email_id"
}
}
}
*/
if (!target_doctype) target_doctype = "*";
// Target field kept as key because source field could be non-unique
this.fetch_dict
.setDefault(target_doctype, {})
.setDefault(link_field, {})[target_field] = source_field;
}
has_perm(ptype) {

View file

@ -196,7 +196,7 @@ frappe.ui.form.ScriptManager = class ScriptManager {
'Text Editor', 'Code', 'Link', 'Float', 'Int', 'Date', 'Select', 'Duration'].includes(df.fieldtype) || df.read_only==1)
&& df.fetch_from && df.fetch_from.indexOf(".")!=-1) {
var parts = df.fetch_from.split(".");
me.frm.add_fetch(parts[0], parts[1], df.fieldname);
me.frm.add_fetch(parts[0], parts[1], df.fieldname, df.parent);
}
}

View file

@ -50,38 +50,31 @@ frappe.search.AwesomeBar = class AwesomeBar {
this.awesomplete = awesomplete;
$input.on("input", function(e) {
$input.on("input", frappe.utils.debounce(function(e) {
var value = e.target.value;
var txt = value.trim().replace(/\s\s+/g, ' ');
var last_space = txt.lastIndexOf(' ');
me.global_results = [];
// if(txt && txt.length > 1) {
// me.global.get_awesome_bar_options(txt.toLowerCase(), me);
// }
var $this = $(this);
clearTimeout($this.data('timeout'));
me.options = [];
$this.data('timeout', setTimeout(function(){
me.options = [];
if(txt && txt.length > 1) {
if(last_space !== -1) {
me.set_specifics(txt.slice(0,last_space), txt.slice(last_space+1));
}
me.add_defaults(txt);
me.options = me.options.concat(me.build_options(txt));
me.options = me.options.concat(me.global_results);
} else {
me.options = me.options.concat(
me.deduplicate(frappe.search.utils.get_recent_pages(txt || "")));
me.options = me.options.concat(frappe.search.utils.get_frequent_links());
if (txt && txt.length > 1) {
if (last_space !== -1) {
me.set_specifics(txt.slice(0, last_space), txt.slice(last_space+1));
}
me.add_help();
me.add_defaults(txt);
me.options = me.options.concat(me.build_options(txt));
me.options = me.options.concat(me.global_results);
} else {
me.options = me.options.concat(
me.deduplicate(frappe.search.utils.get_recent_pages(txt || "")));
me.options = me.options.concat(frappe.search.utils.get_frequent_links());
}
me.add_help();
awesomplete.list = me.deduplicate(me.options);
}, 100));
awesomplete.list = me.deduplicate(me.options);
});
}, 500));
var open_recent = function() {
if (!this.autocomplete_open) {

View file

@ -23,6 +23,14 @@ if (!Array.prototype.uniqBy) {
});
}
// Python's dict.setdefault ported for JS objects
Object.defineProperty(Object.prototype, "setDefault", {
value: function(key, default_value) {
if (!(key in this)) this[key] = default_value;
return this[key];
}
});
// Pluralize
String.prototype.plural = function(revert) {
const plural = {

View file

@ -16,6 +16,8 @@ import frappe.translate
import redis
from urllib.parse import unquote
from frappe.cache_manager import clear_user_cache
from frappe.query_builder import Order, DocType
@frappe.whitelist()
def clear():
@ -61,18 +63,14 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None):
simultaneous_sessions = frappe.db.get_value('User', user, 'simultaneous_sessions') or 1
offset = simultaneous_sessions - 1
condition = ''
session = DocType("Sessions")
session_id = frappe.qb.from_(session).where((session.user == user) & (session.device.isin(device)))
if keep_current:
condition = ' AND sid != {0}'.format(frappe.db.escape(frappe.session.sid))
session_id = session_id.where(session.sid != frappe.db.escape(frappe.session.sid))
return frappe.db.sql_list("""
SELECT `sid` FROM `tabSessions`
WHERE `tabSessions`.user=%(user)s
AND device in %(device)s
{condition}
ORDER BY `lastupdate` DESC
LIMIT 100 OFFSET {offset}""".format(condition=condition, offset=offset),
{"user": user, "device": device})
query = session_id.select(session.sid).offset(offset).limit(100).orderby(session.lastupdate, order=Order.desc)
return query.run(pluck=True)
def delete_session(sid=None, user=None, reason="Session Expired"):
from frappe.core.doctype.activity_log.feed import logout_feed
@ -80,7 +78,10 @@ def delete_session(sid=None, user=None, reason="Session Expired"):
frappe.cache().hdel("session", sid)
frappe.cache().hdel("last_db_session_update", sid)
if sid and not user:
user_details = frappe.db.sql("""select user from tabSessions where sid=%s""", sid, as_dict=True)
table = DocType("Sessions")
user_details = frappe.qb.from_(table).where(
table.sid == sid
).select(table.user).run(as_dict=True)
if user_details: user = user_details[0].get("user")
logout_feed(user, reason)
@ -91,7 +92,7 @@ def clear_all_sessions(reason=None):
"""This effectively logs out all users"""
frappe.only_for("Administrator")
if not reason: reason = "Deleted All Active Session"
for sid in frappe.db.sql_list("select sid from `tabSessions`"):
for sid in frappe.qb.from_("Sessions").select("sid").run(pluck=True):
delete_session(sid, reason=reason)
def get_expired_sessions():

View file

@ -128,8 +128,11 @@ def get_shared_doctypes(user=None):
"""Return list of doctypes in which documents are shared for the given user."""
if not user:
user = frappe.session.user
return frappe.db.sql_list("select distinct share_doctype from tabDocShare where (user=%s or everyone=1)", user)
table = frappe.qb.DocType("DocShare")
query = frappe.qb.from_(table).where(
(table.user == user) | (table.everyone == 1)
).select(table.share_doctype).distinct()
return query.run(pluck=True)
def get_share_name(doctype, name, user, everyone):
if cint(everyone):

View file

@ -4,14 +4,13 @@ import frappe
from frappe.utils import set_request
from frappe.website.serve import get_response, get_response_content
from frappe.website.utils import (build_response, clear_website_cache, get_home_page)
from tenacity import retry, stop_after_attempt, retry_if_exception_type
class TestWebsite(unittest.TestCase):
def setUp(self):
frappe.set_user('Guest')
def tearDown(self):
frappe.db.value_cache = {}
frappe.set_user('Administrator')
def test_home_page(self):
@ -197,13 +196,8 @@ class TestWebsite(unittest.TestCase):
delattr(frappe.hooks, 'page_renderer')
frappe.cache().delete_key('app_hooks')
# TODO: Get rid of this retry logic
# Added since test is flaky and we can't figure out why at this point
@retry(
stop=stop_after_attempt(5), retry=retry_if_exception_type(AssertionError),
)
def test_printview_page(self):
content = get_response_content('/Language/en')
content = get_response_content('/Language/ru')
self.assertIn('<div class="print-format">', content)
self.assertIn('<div>Language</div>', content)

View file

@ -20,6 +20,8 @@ from typing import List, Union, Tuple
import frappe
from frappe.model.utils import InvalidIncludePath, render_include
from frappe.utils import get_bench_path, is_html, strip, strip_html_tags
from frappe.query_builder import Field
from pypika.terms import PseudoColumn
def get_language(lang_list: List = None) -> str:
@ -119,7 +121,8 @@ def set_default_language(lang):
def get_lang_dict():
"""Returns all languages in dict format, full name is the key e.g. `{"english":"en"}`"""
return dict(frappe.db.sql('select language_name, name from tabLanguage'))
result = dict(frappe.get_all("Language", fields=["language_name", "name"], order_by="modified", as_list=True))
return result
def get_dict(fortype, name=None):
"""Returns translation dict for a type of object.
@ -151,12 +154,25 @@ def get_dict(fortype, name=None):
messages += get_messages_from_navbar()
messages += get_messages_from_include_files()
messages += frappe.db.sql("select 'Print Format:', name from `tabPrint Format`")
messages += frappe.db.sql("select 'DocType:', name from tabDocType")
messages += frappe.db.sql("select 'Role:', name from tabRole")
messages += frappe.db.sql("select 'Module:', name from `tabModule Def`")
messages += frappe.db.sql("select '', format from `tabWorkspace Shortcut` where format is not null")
messages += frappe.db.sql("select '', title from `tabOnboarding Step`")
messages += (
frappe.qb.from_("Print Format")
.select(PseudoColumn("'Print Format:'"), "name")).run()
messages += (
frappe.qb.from_("DocType")
.select(PseudoColumn("'DocType:'"), "name")).run()
messages += (
frappe.qb.from_("Role").select(PseudoColumn("'Role:'"), "name").run()
)
messages += (
frappe.qb.from_("Module Def")
.select(PseudoColumn("'Module:'"), "name")).run()
messages += (
frappe.qb.from_("Workspace Shortcut")
.where(Field("format").isnotnull())
.select(PseudoColumn("''"), "format")).run()
messages += (
frappe.qb.from_("Onboarding Step")
.select(PseudoColumn("''"), "title")).run()
messages = deduplicate_messages(messages)
message_dict = make_dict_from_messages(messages, load_user_translation=False)
@ -323,13 +339,17 @@ def get_messages_for_app(app, deduplicate=True):
# doctypes
if modules:
for name in frappe.db.sql_list("""select name from tabDocType
where module in ({})""".format(modules)):
filtered_doctypes = frappe.qb.from_("DocType").where(
Field("module").isin(modules)
).select("name").run()
for name in filtered_doctypes:
messages.extend(get_messages_from_doctype(name))
# pages
for name, title in frappe.db.sql("""select name, title from tabPage
where module in ({})""".format(modules)):
filtered_pages = frappe.qb.from_("Page").where(
Field("module").isin(modules)
).select("name", "title").run()
for name, title in filtered_pages:
messages.append((None, title or name))
messages.extend(get_messages_from_page(name))
@ -898,7 +918,7 @@ def get_translator_url():
def get_all_languages(with_language_name=False):
"""Returns all language codes ar, ch etc"""
def get_language_codes():
return frappe.db.sql_list('select name from tabLanguage')
return frappe.get_all("Language", pluck="name")
def get_all_language_with_name():
return frappe.db.get_all('Language', ['language_code', 'language_name'])