Merge branch 'frappe:develop' into primary-navbar-css-fix
This commit is contained in:
commit
8f0d79d283
26 changed files with 245 additions and 219 deletions
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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", {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)})")
|
||||
))
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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']]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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'])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue