@@ -12,7 +12,7 @@ jobs: | |||
- name: 'Setup Environment' | |||
uses: actions/setup-python@v2 | |||
with: | |||
python-version: 3.6 | |||
python-version: 3.7 | |||
- name: 'Clone repo' | |||
uses: actions/checkout@v2 | |||
@@ -18,7 +18,7 @@ jobs: | |||
node-version: 14 | |||
- uses: actions/setup-python@v2 | |||
with: | |||
python-version: '3.6' | |||
python-version: '3.7' | |||
- name: Set up bench and build assets | |||
run: | | |||
npm install -g yarn | |||
@@ -21,7 +21,7 @@ jobs: | |||
python-version: '12.x' | |||
- uses: actions/setup-python@v2 | |||
with: | |||
python-version: '3.6' | |||
python-version: '3.7' | |||
- name: Set up bench and build assets | |||
run: | | |||
npm install -g yarn | |||
@@ -6,6 +6,28 @@ context('List View', () => { | |||
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow"); | |||
}); | |||
}); | |||
it('Keep checkbox checked after Bulk Update', () => { | |||
cy.go_to_list('ToDo'); | |||
cy.get('.list-row-container .list-row-checkbox').click({ multiple: true, force: true }); | |||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); | |||
cy.get('.dropdown-menu li:visible .dropdown-item .menu-item-label[data-label="Edit"]').click(); | |||
cy.get('.modal-body .form-control[data-fieldname="field"]').first().select('Due Date').wait(200); | |||
cy.get('.modal-body .frappe-control[data-fieldname="value"] input:visible').first().focus(); | |||
cy.get('.datepickers-container .datepicker.active').should('be.visible'); | |||
cy.get('.datepickers-container .datepicker.active .datepicker--cell-day.-current-').click({force: true}); | |||
cy.get('.modal-body .frappe-control[data-fieldname="value"] input:visible').first().focus(); | |||
cy.get('.datepickers-container .datepicker.active .datepicker--cell-day.-current-').click({force: true}); | |||
cy.get('.modal-footer .standard-actions .btn-primary').click(); | |||
cy.wait(500); | |||
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click(); | |||
cy.get('.list-row-container .list-row-checkbox:checked').should('be.visible'); | |||
}); | |||
it('enables "Actions" button', () => { | |||
const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; | |||
cy.go_to_list('ToDo'); | |||
@@ -30,4 +52,3 @@ context('List View', () => { | |||
}); | |||
}); | |||
}); | |||
@@ -235,12 +235,13 @@ def connect_replica(): | |||
from frappe.database import get_db | |||
user = local.conf.db_name | |||
password = local.conf.db_password | |||
port = local.conf.replica_db_port | |||
if local.conf.different_credentials_for_replica: | |||
user = local.conf.replica_db_name | |||
password = local.conf.replica_db_password | |||
local.replica_db = get_db(host=local.conf.replica_host, user=user, password=password) | |||
local.replica_db = get_db(host=local.conf.replica_host, user=user, password=password, port=port) | |||
# swap db connections | |||
local.primary_db = local.db | |||
@@ -1,6 +1,7 @@ | |||
# Copyright (c) 2019, Frappe Technologies and contributors | |||
# Copyright (c) 2021, Frappe Technologies and contributors | |||
# License: MIT. See LICENSE | |||
import frappe | |||
from tenacity import retry, retry_if_exception_type, stop_after_attempt | |||
from frappe.model.document import Document | |||
@@ -10,25 +11,40 @@ class AccessLog(Document): | |||
@frappe.whitelist() | |||
@frappe.write_only() | |||
def make_access_log(doctype=None, document=None, method=None, file_type=None, | |||
report_name=None, filters=None, page=None, columns=None): | |||
@retry( | |||
stop=stop_after_attempt(3), retry=retry_if_exception_type(frappe.DuplicateEntryError) | |||
) | |||
def make_access_log( | |||
doctype=None, | |||
document=None, | |||
method=None, | |||
file_type=None, | |||
report_name=None, | |||
filters=None, | |||
page=None, | |||
columns=None, | |||
): | |||
user = frappe.session.user | |||
in_request = frappe.request and frappe.request.method == "GET" | |||
doc = frappe.get_doc({ | |||
'doctype': 'Access Log', | |||
'user': user, | |||
'export_from': doctype, | |||
'reference_document': document, | |||
'file_type': file_type, | |||
'report_name': report_name, | |||
'page': page, | |||
'method': method, | |||
'filters': frappe.utils.cstr(filters) if filters else None, | |||
'columns': columns | |||
}) | |||
doc = frappe.get_doc( | |||
{ | |||
"doctype": "Access Log", | |||
"user": user, | |||
"export_from": doctype, | |||
"reference_document": document, | |||
"file_type": file_type, | |||
"report_name": report_name, | |||
"page": page, | |||
"method": method, | |||
"filters": frappe.utils.cstr(filters) if filters else None, | |||
"columns": columns, | |||
} | |||
) | |||
doc.insert(ignore_permissions=True) | |||
# `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` | |||
if frappe.request and frappe.request.method == 'GET': | |||
# dont commit in test mode | |||
if not frappe.flags.in_test or in_request: | |||
frappe.db.commit() |
@@ -332,7 +332,7 @@ class Database(object): | |||
values[key] = value | |||
if isinstance(value, (list, tuple)): | |||
# value is a tuple like ("!=", 0) | |||
_operator = value[0] | |||
_operator = value[0].lower() | |||
values[key] = value[1] | |||
if isinstance(value[1], (tuple, list)): | |||
# value is a list in tuple ("in", ("A", "B")) | |||
@@ -1,322 +1,106 @@ | |||
{ | |||
"allow_copy": 0, | |||
"allow_guest_to_view": 0, | |||
"allow_import": 0, | |||
"allow_rename": 1, | |||
"beta": 0, | |||
"creation": "2013-05-24 13:41:00", | |||
"custom": 0, | |||
"description": "", | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"document_type": "Document", | |||
"editable_grid": 0, | |||
"fields": [ | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "title", | |||
"fieldtype": "Data", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 1, | |||
"in_list_view": 1, | |||
"in_standard_filter": 0, | |||
"label": "Title", | |||
"length": 0, | |||
"no_copy": 1, | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 1, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"description": "", | |||
"fieldname": "public", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Public", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 1, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 1, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"depends_on": "public", | |||
"fieldname": "notify_on_login", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Notify users with a popup when they log in", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 1, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"default": "0", | |||
"depends_on": "notify_on_login", | |||
"description": "If enabled, users will be notified every time they login. If not enabled, users will only be notified once.", | |||
"fieldname": "notify_on_every_login", | |||
"fieldtype": "Check", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Notify Users On Every Login", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"depends_on": "eval:doc.notify_on_login && doc.public", | |||
"fieldname": "expire_notification_on", | |||
"fieldtype": "Date", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Expire Notification On", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 1, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 1, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"description": "Help: To link to another record in the system, use \"#Form/Note/[Note Name]\" as the Link URL. (don't use \"http://\")", | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 1, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Content", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 1, | |||
"columns": 0, | |||
"fieldname": "seen_by_section", | |||
"fieldtype": "Section Break", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Seen By", | |||
"length": 0, | |||
"no_copy": 0, | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
}, | |||
{ | |||
"allow_bulk_edit": 0, | |||
"allow_in_quick_entry": 0, | |||
"allow_on_submit": 0, | |||
"bold": 0, | |||
"collapsible": 0, | |||
"columns": 0, | |||
"fieldname": "seen_by", | |||
"fieldtype": "Table", | |||
"hidden": 0, | |||
"ignore_user_permissions": 0, | |||
"ignore_xss_filter": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"label": "Seen By Table", | |||
"length": 0, | |||
"no_copy": 0, | |||
"options": "Note Seen By", | |||
"permlevel": 0, | |||
"precision": "", | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"read_only": 0, | |||
"remember_last_selected_value": 0, | |||
"report_hide": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"set_only_once": 0, | |||
"translatable": 0, | |||
"unique": 0 | |||
} | |||
], | |||
"has_web_view": 0, | |||
"hide_heading": 0, | |||
"hide_toolbar": 0, | |||
"icon": "fa fa-file-text", | |||
"idx": 1, | |||
"image_view": 0, | |||
"in_create": 0, | |||
"is_submittable": 0, | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2018-09-21 15:15:44.909636", | |||
"modified_by": "Administrator", | |||
"module": "Desk", | |||
"name": "Note", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"export": 0, | |||
"if_owner": 0, | |||
"import": 0, | |||
"permlevel": 0, | |||
"print": 1, | |||
"read": 1, | |||
"report": 0, | |||
"role": "All", | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"submit": 0, | |||
"write": 1 | |||
} | |||
], | |||
"quick_entry": 1, | |||
"read_only": 0, | |||
"read_only_onload": 1, | |||
"show_name_in_global_search": 0, | |||
"sort_order": "ASC", | |||
"track_changes": 1, | |||
"track_seen": 0, | |||
"track_views": 0 | |||
} | |||
"actions": [], | |||
"allow_rename": 1, | |||
"creation": "2013-05-24 13:41:00", | |||
"doctype": "DocType", | |||
"document_type": "Document", | |||
"engine": "InnoDB", | |||
"field_order": [ | |||
"title", | |||
"public", | |||
"notify_on_login", | |||
"notify_on_every_login", | |||
"expire_notification_on", | |||
"content", | |||
"seen_by_section", | |||
"seen_by" | |||
], | |||
"fields": [ | |||
{ | |||
"fieldname": "title", | |||
"fieldtype": "Data", | |||
"in_global_search": 1, | |||
"in_list_view": 1, | |||
"label": "Title", | |||
"no_copy": 1, | |||
"print_hide": 1, | |||
"reqd": 1 | |||
}, | |||
{ | |||
"bold": 1, | |||
"default": "0", | |||
"fieldname": "public", | |||
"fieldtype": "Check", | |||
"label": "Public", | |||
"print_hide": 1 | |||
}, | |||
{ | |||
"bold": 1, | |||
"default": "0", | |||
"depends_on": "public", | |||
"fieldname": "notify_on_login", | |||
"fieldtype": "Check", | |||
"label": "Notify users with a popup when they log in" | |||
}, | |||
{ | |||
"bold": 1, | |||
"default": "0", | |||
"depends_on": "notify_on_login", | |||
"description": "If enabled, users will be notified every time they login. If not enabled, users will only be notified once.", | |||
"fieldname": "notify_on_every_login", | |||
"fieldtype": "Check", | |||
"label": "Notify Users On Every Login" | |||
}, | |||
{ | |||
"depends_on": "eval:doc.notify_on_login && doc.public", | |||
"fieldname": "expire_notification_on", | |||
"fieldtype": "Date", | |||
"label": "Expire Notification On", | |||
"search_index": 1 | |||
}, | |||
{ | |||
"bold": 1, | |||
"description": "Help: To link to another record in the system, use \"/app/note/[Note Name]\" as the Link URL. (don't use \"http://\")", | |||
"fieldname": "content", | |||
"fieldtype": "Text Editor", | |||
"in_global_search": 1, | |||
"label": "Content" | |||
}, | |||
{ | |||
"collapsible": 1, | |||
"fieldname": "seen_by_section", | |||
"fieldtype": "Section Break", | |||
"label": "Seen By" | |||
}, | |||
{ | |||
"fieldname": "seen_by", | |||
"fieldtype": "Table", | |||
"label": "Seen By Table", | |||
"options": "Note Seen By" | |||
} | |||
], | |||
"icon": "fa fa-file-text", | |||
"idx": 1, | |||
"links": [], | |||
"modified": "2021-09-18 10:57:51.352643", | |||
"modified_by": "Administrator", | |||
"module": "Desk", | |||
"name": "Note", | |||
"owner": "Administrator", | |||
"permissions": [ | |||
{ | |||
"create": 1, | |||
"delete": 1, | |||
"email": 1, | |||
"print": 1, | |||
"read": 1, | |||
"role": "All", | |||
"share": 1, | |||
"write": 1 | |||
} | |||
], | |||
"quick_entry": 1, | |||
"sort_field": "modified", | |||
"sort_order": "ASC", | |||
"track_changes": 1 | |||
} |
@@ -4,6 +4,7 @@ | |||
from typing import List | |||
import frappe.defaults | |||
from frappe.query_builder.utils import Column | |||
import frappe.share | |||
from frappe import _ | |||
import frappe.permissions | |||
@@ -491,7 +492,7 @@ class DatabaseQuery(object): | |||
f.value = date_range | |||
fallback = "'0001-01-01 00:00:00'" | |||
if f.operator in ('>', '<') and (f.fieldname in ('creation', 'modified')): | |||
if (f.fieldname in ('creation', 'modified')): | |||
value = cstr(f.value) | |||
fallback = "NULL" | |||
@@ -547,8 +548,12 @@ class DatabaseQuery(object): | |||
value = flt(f.value) | |||
fallback = 0 | |||
if isinstance(f.value, Column): | |||
quote = '"' if frappe.conf.db_type == 'postgres' else "`" | |||
value = f"{tname}.{quote}{f.value.name}{quote}" | |||
# escape value | |||
if isinstance(value, str) and not f.operator.lower() == 'between': | |||
elif isinstance(value, str) and not f.operator.lower() == 'between': | |||
value = f"{frappe.db.escape(value, percent=False)}" | |||
if ( | |||
@@ -212,13 +212,12 @@ export default class Grid { | |||
delete_all_rows() { | |||
frappe.confirm(__("Are you sure you want to delete all rows?"), () => { | |||
this.grid_rows.forEach(row => { | |||
row.remove(); | |||
}); | |||
this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); | |||
this.wrapper.find('.grid-heading-row .grid-row-check:checked:first').prop('checked', 0); | |||
this.frm.doc[this.df.fieldname] = []; | |||
$(this.parent).find('.rows').empty(); | |||
this.grid_rows = []; | |||
this.refresh(); | |||
this.frm && this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype); | |||
this.frm && this.frm.dirty(); | |||
this.scroll_to_top(); | |||
}); | |||
} | |||
@@ -244,8 +243,10 @@ export default class Grid { | |||
this.remove_rows_button.toggleClass('hidden', | |||
this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true); | |||
this.remove_all_rows_button.toggleClass('hidden', | |||
this.wrapper.find('.grid-heading-row .grid-row-check:checked:first').length ? false : true); | |||
let select_all_checkbox_checked = this.wrapper.find('.grid-heading-row .grid-row-check:checked:first').length; | |||
let show_delete_all_btn = select_all_checkbox_checked && this.data.length > this.get_selected_children().length; | |||
this.remove_all_rows_button.toggleClass('hidden', !show_delete_all_btn); | |||
} | |||
get_selected() { | |||
@@ -835,10 +836,11 @@ export default class Grid { | |||
$.each(row, (ci, value) => { | |||
var fieldname = fieldnames[ci]; | |||
var df = frappe.meta.get_docfield(me.df.options, fieldname); | |||
d[fieldnames[ci]] = value_formatter_map[df.fieldtype] | |||
? value_formatter_map[df.fieldtype](value) | |||
: value; | |||
if (df) { | |||
d[fieldnames[ci]] = value_formatter_map[df.fieldtype] | |||
? value_formatter_map[df.fieldtype](value) | |||
: value; | |||
} | |||
}); | |||
} | |||
} | |||
@@ -483,7 +483,8 @@ frappe.ui.form.Layout = class Layout { | |||
// next row | |||
grid_row.grid.grid_rows[grid_row.doc.idx].toggle_view(true); | |||
} | |||
} else { | |||
} else if (!shift) { | |||
// End of tab navigation | |||
$(this.primary_button).focus(); | |||
} | |||
} | |||
@@ -572,6 +572,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
render() { | |||
this.render_list(); | |||
this.set_rows_as_checked(); | |||
this.on_row_checked(); | |||
this.render_count(); | |||
} | |||
@@ -607,9 +608,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
const subject_field = this.columns[0].df; | |||
let subject_html = ` | |||
<input class="level-item list-check-all hidden-xs" type="checkbox" | |||
<input class="level-item list-check-all" type="checkbox" | |||
title="${__("Select All")}"> | |||
<span class="level-item list-liked-by-me"> | |||
<span class="level-item list-liked-by-me hidden-xs"> | |||
<span title="${__("Likes")}">${frappe.utils.icon('heart', 'sm', 'like-icon')}</span> | |||
</span> | |||
<span class="level-item">${__(subject_field.label)}</span> | |||
@@ -646,7 +647,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
</div> | |||
<div class="level-left checkbox-actions"> | |||
<div class="level list-subject"> | |||
<input class="level-item list-check-all hidden-xs" type="checkbox" | |||
<input class="level-item list-check-all" type="checkbox" | |||
title="${__("Select All")}"> | |||
<span class="level-item list-header-meta"></span> | |||
</div> | |||
@@ -954,9 +955,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { | |||
let subject_html = ` | |||
<span class="level-item select-like"> | |||
<input class="list-row-checkbox hidden-xs" type="checkbox" | |||
<input class="list-row-checkbox" type="checkbox" | |||
data-name="${escape(doc.name)}"> | |||
<span class="list-row-like style="margin-bottom: 1px;"> | |||
<span class="list-row-like hidden-xs style="margin-bottom: 1px;"> | |||
${this.get_like_html(doc)} | |||
</span> | |||
</span> | |||
@@ -927,7 +927,16 @@ Object.assign(frappe.utils, { | |||
// decodes base64 to string | |||
let parts = dataURI.split(','); | |||
const encoded_data = parts[1]; | |||
return decodeURIComponent(escape(atob(encoded_data))); | |||
let decoded = atob(encoded_data); | |||
try { | |||
const escaped = escape(decoded); | |||
decoded = decodeURIComponent(escaped); | |||
} catch (e) { | |||
// pass decodeURIComponent failure | |||
// just return atob response | |||
} | |||
return decoded; | |||
}, | |||
copy_to_clipboard(string) { | |||
let input = $("<input>"); | |||
@@ -202,8 +202,12 @@ body { | |||
} | |||
// listviews | |||
.list-row { | |||
padding: 13px 15px !important; | |||
.select-like { | |||
margin-right: unset !important; | |||
} | |||
.list-count { | |||
display: contents; | |||
} | |||
.doclist-row { | |||
@@ -1,2 +1,2 @@ | |||
from pypika import * | |||
from frappe.query_builder.utils import get_query_builder, patch_query_execute | |||
from frappe.query_builder.utils import Column, get_query_builder, patch_query_execute |
@@ -3,8 +3,10 @@ from typing import Any, Callable, Dict, get_type_hints | |||
from importlib import import_module | |||
from pypika import Query | |||
from pypika.queries import Column | |||
import frappe | |||
from .builder import MariaDB, Postgres | |||
@@ -1,31 +1,73 @@ | |||
.hero-and-content { | |||
background-color: #f5f7fa; | |||
background-color: var(--bg-color); | |||
} | |||
body { | |||
background-color: var(--bg-color); | |||
} | |||
.page-card { | |||
max-width: 360px; | |||
padding: 15px; | |||
margin: 70px auto; | |||
border: 1px solid #d1d8dd; | |||
border-radius: 4px; | |||
background-color: #fff; | |||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1); | |||
background-color: var(--fg-color); | |||
box-shadow: var(--shadow-base); | |||
} | |||
.for-reset-password { | |||
margin: 80px 0; | |||
} | |||
.for-reset-password .page-card { | |||
border: 0; | |||
max-width: 450px; | |||
margin: auto; | |||
padding: 40px 60px; | |||
border-radius: 10px; | |||
box-shadow: var(--shadow-base); | |||
} | |||
.page-card .page-card-head { | |||
padding: 10px 15px; | |||
margin: -15px; | |||
margin-bottom: 15px; | |||
border-bottom: 1px solid #d1d8dd; | |||
border-bottom: 1px solid var(--border-color); | |||
} | |||
.for-reset-password .page-card .page-card-head { | |||
border-bottom: 0; | |||
} | |||
.page-card-head h4 { | |||
font-size: 18px; | |||
font-weight: 600; | |||
} | |||
#reset-password .form-group { | |||
margin-bottom: 10px; | |||
font-size: var(--font-size-sm); | |||
} | |||
.page-card .page-card-head .indicator { | |||
color: #36414C; | |||
font-size: 14px; | |||
} | |||
.sign-up-message { | |||
margin-top: 20px; | |||
font-size: 13px; | |||
color: var(--text-color); | |||
} | |||
.page-card .page-card-head .indicator::before { | |||
margin: 0 6px 0.5px 0px; | |||
} | |||
button#update { | |||
font-size: var(--font-size-sm); | |||
} | |||
.page-card .btn { | |||
margin-top: 30px; | |||
} | |||
.page-card p { | |||
font-size: 14px; | |||
} | |||
@@ -4,6 +4,7 @@ import frappe, unittest | |||
from frappe.model.db_query import DatabaseQuery | |||
from frappe.desk.reportview import get_filters_cond | |||
from frappe.query_builder import Column | |||
from frappe.core.page.permission_manager.permission_manager import update, reset, add | |||
from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype | |||
@@ -373,6 +374,25 @@ class TestReportview(unittest.TestCase): | |||
owners = DatabaseQuery("DocType").execute(filters={"name": "DocType"}, pluck="owner") | |||
self.assertEqual(owners, ["Administrator"]) | |||
def test_column_comparison(self): | |||
"""Test DatabaseQuery.execute to test column comparison | |||
""" | |||
users_unedited = frappe.get_all( | |||
"User", | |||
filters={"creation": Column("modified")}, | |||
fields=["name", "creation", "modified"], | |||
limit=1, | |||
) | |||
users_edited = frappe.get_all( | |||
"User", | |||
filters={"creation": ("!=", Column("modified"))}, | |||
fields=["name", "creation", "modified"], | |||
limit=1, | |||
) | |||
self.assertEqual(users_unedited[0].modified, users_unedited[0].creation) | |||
self.assertNotEqual(users_edited[0].modified, users_edited[0].creation) | |||
def test_reportview_get(self): | |||
user = frappe.get_doc("User", "test@example.com") | |||
add_child_table_to_blog_post() | |||
@@ -9,9 +9,10 @@ from frappe.desk.form.utils import get_pdf_link | |||
from frappe.utils.verified_command import get_signed_params, verify_request | |||
from frappe import _ | |||
from frappe.model.workflow import apply_workflow, get_workflow_name, has_approval_access, \ | |||
get_workflow_state_field, send_email_alert, get_workflow_field_value, is_transition_condition_satisfied | |||
get_workflow_state_field, send_email_alert, is_transition_condition_satisfied | |||
from frappe.desk.notifications import clear_doctype_notifications | |||
from frappe.utils.user import get_users_with_role | |||
from frappe.utils.data import get_link_to_form | |||
class WorkflowAction(Document): | |||
pass | |||
@@ -287,12 +288,12 @@ def get_common_email_args(doc): | |||
response = frappe.render_template(email_template.response, vars(doc)) | |||
else: | |||
subject = _('Workflow Action') | |||
response = _('{0}: {1}').format(doctype, docname) | |||
response = get_link_to_form(doctype, docname, f"{doctype}: {docname}") | |||
common_args = { | |||
'template': 'workflow_action', | |||
'header': 'Workflow Action', | |||
'attachments': [frappe.attach_print(doctype, docname , file_name=docname)], | |||
'attachments': [frappe.attach_print(doctype, docname, file_name=docname, doc=doc)], | |||
'subject': subject, | |||
'message': response | |||
} | |||
@@ -1,32 +1,38 @@ | |||
{% extends "templates/web.html" %} | |||
{% block title %} {{_("Reset Password")}} {% endblock %} | |||
{% block head_include %} | |||
{% endblock %} | |||
{% block page_content %} | |||
<div class="page-card"> | |||
<div class='page-card-head'> | |||
<span class='indicator blue password-box'>{{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}</span> | |||
</div> | |||
<form id="reset-password"> | |||
<div class="form-group" style="display: none;"> | |||
<input id="old_password" type="password" | |||
class="form-control" placeholder="{{ _("Old Password") }}"> | |||
</div> | |||
<div class="form-group"> | |||
<input id="new_password" type="password" | |||
class="form-control" placeholder="{{ _("New Password") }}"> | |||
<span class="password-strength-indicator indicator"></span> | |||
<section class="for-reset-password d-block"> | |||
<div class="page-card"> | |||
<div class='page-card-head text-center'> | |||
<h4 class="reset-password-heading">{{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}</h4> | |||
</div> | |||
<p class='password-strength-message text-muted small hidden'></p> | |||
<button type="submit" id="update" | |||
class="btn btn-primary">{{_("Update")}}</button> | |||
</form> | |||
</div> | |||
<form id="reset-password"> | |||
<div class="form-group" style="display: none;"> | |||
<input id="old_password" type="password" | |||
class="form-control" placeholder="{{ _("Old Password") }}"> | |||
</div> | |||
<div class="form-group"> | |||
<input id="new_password" type="password" | |||
class="form-control" placeholder="{{ _("New Password") }}"> | |||
<span class="password-strength-indicator indicator"></span> | |||
</div> | |||
<p class='password-strength-message text-muted small hidden'></p> | |||
<button type="submit" id="update" | |||
class="btn btn-primary btn-block btn-update">{{_("Confirm")}}</button> | |||
</form> | |||
{%- if not disable_signup -%} | |||
<div class="text-center sign-up-message"> | |||
{{ _("Don't have an account?") }} | |||
<a href="/login#signup">{{ _("Sign up") }}</a> | |||
</div> | |||
{%- endif -%} | |||
</div> | |||
</section> | |||
<style> | |||
.hero-and-content { | |||
background-color: #f5f7fa; | |||
} | |||
</style> | |||
<script> | |||
@@ -69,20 +75,19 @@ frappe.ready(function() { | |||
args: args, | |||
statusCode: { | |||
401: function() { | |||
$(".page-card-head .indicator").removeClass().addClass("indicator red").text(__("Invalid Password")); | |||
$(".page-card-head .reset-password-heading").text(__("Invalid Password")); | |||
}, | |||
410: function({ responseJSON }) { | |||
const title = __("Invalid Link"); | |||
const message = responseJSON.message; | |||
$(".page-card-head .indicator").removeClass().addClass("indicator grey").text(title); | |||
$(".page-card-head .reset-password-heading").text(title); | |||
frappe.msgprint({ title: title, message: message, clear: true }); | |||
}, | |||
200: function(r) { | |||
$("input").val(""); | |||
strength_indicator.addClass("hidden"); | |||
strength_message.addClass("hidden"); | |||
$(".page-card-head .indicator") | |||
.removeClass().addClass("indicator blue") | |||
$(".page-card-head .reset-password-heading") | |||
.html(__("Status Updated")); | |||
if(r.message) { | |||
frappe.msgprint({ | |||
@@ -132,7 +137,7 @@ frappe.ready(function() { | |||
}, | |||
statusCode: { | |||
401: function() { | |||
$('.page-card-head .indicator').removeClass().addClass('indicator red') | |||
$('.page-card-head .reset-password-heading') | |||
.text("{{ _('Invalid Password') }}"); | |||
}, | |||
200: function(r) { | |||
@@ -175,7 +180,6 @@ frappe.ready(function() { | |||
} | |||
} | |||
strength_indicator.removeClass().addClass('password-strength-indicator indicator ' + color); | |||
strength_message.html(message.join(' ') || '').removeClass('hidden'); | |||
// strength_indicator.attr('title', message.join(' ') || ''); | |||
} | |||
@@ -24,8 +24,7 @@ googlemaps~=4.4.5 | |||
gunicorn~=20.1.0 | |||
html2text==2020.1.16 | |||
html5lib~=1.1 | |||
ipython~=7.16.1 | |||
jedi==0.17.2 # not directly required. Pinned to fix upstream IPython issue (https://github.com/ipython/ipython/issues/12740) | |||
ipython~=7.27.0 | |||
Jinja2~=3.0.1 | |||
ldap3~=2.9 | |||
markdown2~=2.4.0 | |||
@@ -57,5 +57,5 @@ setup( | |||
{ | |||
'clean': CleanCommand | |||
}, | |||
python_requires='>=3.6' | |||
python_requires='>=3.7' | |||
) |