Bladeren bron

Merge branch 'develop' of https://github.com/frappe/frappe into form-tab-break

version-14
Suraj Shetty 3 jaren geleden
bovenliggende
commit
2f427a82c8
22 gewijzigde bestanden met toevoegingen van 319 en 407 verwijderingen
  1. +1
    -1
      .github/workflows/docs-checker.yml
  2. +1
    -1
      .github/workflows/publish-assets-develop.yml
  3. +1
    -1
      .github/workflows/publish-assets-releases.yml
  4. +22
    -1
      cypress/integration/list_view.js
  5. +2
    -1
      frappe/__init__.py
  6. +32
    -16
      frappe/core/doctype/access_log/access_log.py
  7. +1
    -1
      frappe/database/database.py
  8. +105
    -321
      frappe/desk/doctype/note/note.json
  9. +7
    -2
      frappe/model/db_query.py
  10. +14
    -12
      frappe/public/js/frappe/form/grid.js
  11. +2
    -1
      frappe/public/js/frappe/form/layout.js
  12. +6
    -5
      frappe/public/js/frappe/list/list_view.js
  13. +10
    -1
      frappe/public/js/frappe/utils/utils.js
  14. +6
    -2
      frappe/public/scss/desk/mobile.scss
  15. +1
    -1
      frappe/query_builder/__init__.py
  16. +2
    -0
      frappe/query_builder/utils.py
  17. +47
    -5
      frappe/templates/styles/card_style.css
  18. +20
    -0
      frappe/tests/test_db_query.py
  19. +4
    -3
      frappe/workflow/doctype/workflow_action/workflow_action.py
  20. +33
    -29
      frappe/www/update-password.html
  21. +1
    -2
      requirements.txt
  22. +1
    -1
      setup.py

+ 1
- 1
.github/workflows/docs-checker.yml Bestand weergeven

@@ -12,7 +12,7 @@ jobs:
- name: 'Setup Environment' - name: 'Setup Environment'
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.6
python-version: 3.7


- name: 'Clone repo' - name: 'Clone repo'
uses: actions/checkout@v2 uses: actions/checkout@v2


+ 1
- 1
.github/workflows/publish-assets-develop.yml Bestand weergeven

@@ -18,7 +18,7 @@ jobs:
node-version: 14 node-version: 14
- uses: actions/setup-python@v2 - uses: actions/setup-python@v2
with: with:
python-version: '3.6'
python-version: '3.7'
- name: Set up bench and build assets - name: Set up bench and build assets
run: | run: |
npm install -g yarn npm install -g yarn


+ 1
- 1
.github/workflows/publish-assets-releases.yml Bestand weergeven

@@ -21,7 +21,7 @@ jobs:
python-version: '12.x' python-version: '12.x'
- uses: actions/setup-python@v2 - uses: actions/setup-python@v2
with: with:
python-version: '3.6'
python-version: '3.7'
- name: Set up bench and build assets - name: Set up bench and build assets
run: | run: |
npm install -g yarn npm install -g yarn


+ 22
- 1
cypress/integration/list_view.js Bestand weergeven

@@ -6,6 +6,28 @@ context('List View', () => {
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow"); 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', () => { it('enables "Actions" button', () => {
const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete']; const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete'];
cy.go_to_list('ToDo'); cy.go_to_list('ToDo');
@@ -30,4 +52,3 @@ context('List View', () => {
}); });
}); });
}); });


+ 2
- 1
frappe/__init__.py Bestand weergeven

@@ -235,12 +235,13 @@ def connect_replica():
from frappe.database import get_db from frappe.database import get_db
user = local.conf.db_name user = local.conf.db_name
password = local.conf.db_password password = local.conf.db_password
port = local.conf.replica_db_port


if local.conf.different_credentials_for_replica: if local.conf.different_credentials_for_replica:
user = local.conf.replica_db_name user = local.conf.replica_db_name
password = local.conf.replica_db_password 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 # swap db connections
local.primary_db = local.db local.primary_db = local.db


+ 32
- 16
frappe/core/doctype/access_log/access_log.py Bestand weergeven

@@ -1,6 +1,7 @@
# Copyright (c) 2019, Frappe Technologies and contributors
# Copyright (c) 2021, Frappe Technologies and contributors
# License: MIT. See LICENSE # License: MIT. See LICENSE
import frappe import frappe
from tenacity import retry, retry_if_exception_type, stop_after_attempt
from frappe.model.document import Document from frappe.model.document import Document




@@ -10,25 +11,40 @@ class AccessLog(Document):


@frappe.whitelist() @frappe.whitelist()
@frappe.write_only() @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 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) doc.insert(ignore_permissions=True)


# `frappe.db.commit` added because insert doesnt `commit` when called in GET requests like `printview` # `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() frappe.db.commit()

+ 1
- 1
frappe/database/database.py Bestand weergeven

@@ -332,7 +332,7 @@ class Database(object):
values[key] = value values[key] = value
if isinstance(value, (list, tuple)): if isinstance(value, (list, tuple)):
# value is a tuple like ("!=", 0) # value is a tuple like ("!=", 0)
_operator = value[0]
_operator = value[0].lower()
values[key] = value[1] values[key] = value[1]
if isinstance(value[1], (tuple, list)): if isinstance(value[1], (tuple, list)):
# value is a list in tuple ("in", ("A", "B")) # value is a list in tuple ("in", ("A", "B"))


+ 105
- 321
frappe/desk/doctype/note/note.json Bestand weergeven

@@ -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
}

+ 7
- 2
frappe/model/db_query.py Bestand weergeven

@@ -4,6 +4,7 @@


from typing import List from typing import List
import frappe.defaults import frappe.defaults
from frappe.query_builder.utils import Column
import frappe.share import frappe.share
from frappe import _ from frappe import _
import frappe.permissions import frappe.permissions
@@ -491,7 +492,7 @@ class DatabaseQuery(object):
f.value = date_range f.value = date_range
fallback = "'0001-01-01 00:00:00'" 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) value = cstr(f.value)
fallback = "NULL" fallback = "NULL"


@@ -547,8 +548,12 @@ class DatabaseQuery(object):
value = flt(f.value) value = flt(f.value)
fallback = 0 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 # 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)}" value = f"{frappe.db.escape(value, percent=False)}"


if ( if (


+ 14
- 12
frappe/public/js/frappe/form/grid.js Bestand weergeven

@@ -212,13 +212,12 @@ export default class Grid {


delete_all_rows() { delete_all_rows() {
frappe.confirm(__("Are you sure you want to 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.refresh();
this.frm && this.frm.script_manager.trigger(this.df.fieldname + "_delete", this.doctype);
this.frm && this.frm.dirty();
this.scroll_to_top(); this.scroll_to_top();
}); });
} }
@@ -244,8 +243,10 @@ export default class Grid {


this.remove_rows_button.toggleClass('hidden', this.remove_rows_button.toggleClass('hidden',
this.wrapper.find('.grid-body .grid-row-check:checked:first').length ? false : true); 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() { get_selected() {
@@ -835,10 +836,11 @@ export default class Grid {
$.each(row, (ci, value) => { $.each(row, (ci, value) => {
var fieldname = fieldnames[ci]; var fieldname = fieldnames[ci];
var df = frappe.meta.get_docfield(me.df.options, fieldname); 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;
}
}); });
} }
} }


+ 2
- 1
frappe/public/js/frappe/form/layout.js Bestand weergeven

@@ -483,7 +483,8 @@ frappe.ui.form.Layout = class Layout {
// next row // next row
grid_row.grid.grid_rows[grid_row.doc.idx].toggle_view(true); grid_row.grid.grid_rows[grid_row.doc.idx].toggle_view(true);
} }
} else {
} else if (!shift) {
// End of tab navigation
$(this.primary_button).focus(); $(this.primary_button).focus();
} }
} }


+ 6
- 5
frappe/public/js/frappe/list/list_view.js Bestand weergeven

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


render() { render() {
this.render_list(); this.render_list();
this.set_rows_as_checked();
this.on_row_checked(); this.on_row_checked();
this.render_count(); this.render_count();
} }
@@ -607,9 +608,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {


const subject_field = this.columns[0].df; const subject_field = this.columns[0].df;
let subject_html = ` 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")}"> 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 title="${__("Likes")}">${frappe.utils.icon('heart', 'sm', 'like-icon')}</span>
</span> </span>
<span class="level-item">${__(subject_field.label)}</span> <span class="level-item">${__(subject_field.label)}</span>
@@ -646,7 +647,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
</div> </div>
<div class="level-left checkbox-actions"> <div class="level-left checkbox-actions">
<div class="level list-subject"> <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")}"> title="${__("Select All")}">
<span class="level-item list-header-meta"></span> <span class="level-item list-header-meta"></span>
</div> </div>
@@ -954,9 +955,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {


let subject_html = ` let subject_html = `
<span class="level-item select-like"> <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)}"> 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)} ${this.get_like_html(doc)}
</span> </span>
</span> </span>


+ 10
- 1
frappe/public/js/frappe/utils/utils.js Bestand weergeven

@@ -927,7 +927,16 @@ Object.assign(frappe.utils, {
// decodes base64 to string // decodes base64 to string
let parts = dataURI.split(','); let parts = dataURI.split(',');
const encoded_data = parts[1]; 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) { copy_to_clipboard(string) {
let input = $("<input>"); let input = $("<input>");


+ 6
- 2
frappe/public/scss/desk/mobile.scss Bestand weergeven

@@ -202,8 +202,12 @@ body {
} }


// listviews // listviews
.list-row {
padding: 13px 15px !important;
.select-like {
margin-right: unset !important;
}

.list-count {
display: contents;
} }


.doclist-row { .doclist-row {


+ 1
- 1
frappe/query_builder/__init__.py Bestand weergeven

@@ -1,2 +1,2 @@
from pypika import * 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

+ 2
- 0
frappe/query_builder/utils.py Bestand weergeven

@@ -3,8 +3,10 @@ from typing import Any, Callable, Dict, get_type_hints
from importlib import import_module from importlib import import_module


from pypika import Query from pypika import Query
from pypika.queries import Column


import frappe import frappe

from .builder import MariaDB, Postgres from .builder import MariaDB, Postgres






+ 47
- 5
frappe/templates/styles/card_style.css Bestand weergeven

@@ -1,31 +1,73 @@
.hero-and-content { .hero-and-content {
background-color: #f5f7fa;
background-color: var(--bg-color);
}

body {
background-color: var(--bg-color);
} }
.page-card { .page-card {
max-width: 360px; max-width: 360px;
padding: 15px; padding: 15px;
margin: 70px auto; margin: 70px auto;
border: 1px solid #d1d8dd;
border-radius: 4px; 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 { .page-card .page-card-head {
padding: 10px 15px; padding: 10px 15px;
margin: -15px; margin: -15px;
margin-bottom: 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 { .page-card .page-card-head .indicator {
color: #36414C; color: #36414C;
font-size: 14px; font-size: 14px;
} }

.sign-up-message {
margin-top: 20px;
font-size: 13px;
color: var(--text-color);
}
.page-card .page-card-head .indicator::before { .page-card .page-card-head .indicator::before {
margin: 0 6px 0.5px 0px; margin: 0 6px 0.5px 0px;
} }

button#update {
font-size: var(--font-size-sm);
}
.page-card .btn { .page-card .btn {
margin-top: 30px; margin-top: 30px;
} }

.page-card p { .page-card p {
font-size: 14px; font-size: 14px;
} }


+ 20
- 0
frappe/tests/test_db_query.py Bestand weergeven

@@ -4,6 +4,7 @@ import frappe, unittest


from frappe.model.db_query import DatabaseQuery from frappe.model.db_query import DatabaseQuery
from frappe.desk.reportview import get_filters_cond 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.core.page.permission_manager.permission_manager import update, reset, add
from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype 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") owners = DatabaseQuery("DocType").execute(filters={"name": "DocType"}, pluck="owner")
self.assertEqual(owners, ["Administrator"]) 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): def test_reportview_get(self):
user = frappe.get_doc("User", "test@example.com") user = frappe.get_doc("User", "test@example.com")
add_child_table_to_blog_post() add_child_table_to_blog_post()


+ 4
- 3
frappe/workflow/doctype/workflow_action/workflow_action.py Bestand weergeven

@@ -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.utils.verified_command import get_signed_params, verify_request
from frappe import _ from frappe import _
from frappe.model.workflow import apply_workflow, get_workflow_name, has_approval_access, \ 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.desk.notifications import clear_doctype_notifications
from frappe.utils.user import get_users_with_role from frappe.utils.user import get_users_with_role
from frappe.utils.data import get_link_to_form


class WorkflowAction(Document): class WorkflowAction(Document):
pass pass
@@ -287,12 +288,12 @@ def get_common_email_args(doc):
response = frappe.render_template(email_template.response, vars(doc)) response = frappe.render_template(email_template.response, vars(doc))
else: else:
subject = _('Workflow Action') subject = _('Workflow Action')
response = _('{0}: {1}').format(doctype, docname)
response = get_link_to_form(doctype, docname, f"{doctype}: {docname}")


common_args = { common_args = {
'template': 'workflow_action', 'template': 'workflow_action',
'header': '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, 'subject': subject,
'message': response 'message': response
} }


+ 33
- 29
frappe/www/update-password.html Bestand weergeven

@@ -1,32 +1,38 @@
{% extends "templates/web.html" %} {% extends "templates/web.html" %}


{% block title %} {{_("Reset Password")}} {% endblock %} {% block title %} {{_("Reset Password")}} {% endblock %}

{% block head_include %}
{% endblock %}
{% block page_content %} {% 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> </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> <style>
.hero-and-content {
background-color: #f5f7fa;
}
</style> </style>


<script> <script>
@@ -69,20 +75,19 @@ frappe.ready(function() {
args: args, args: args,
statusCode: { statusCode: {
401: function() { 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 }) { 410: function({ responseJSON }) {
const title = __("Invalid Link"); const title = __("Invalid Link");
const message = responseJSON.message; 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 }); frappe.msgprint({ title: title, message: message, clear: true });
}, },
200: function(r) { 200: function(r) {
$("input").val(""); $("input").val("");
strength_indicator.addClass("hidden"); strength_indicator.addClass("hidden");
strength_message.addClass("hidden"); strength_message.addClass("hidden");
$(".page-card-head .indicator")
.removeClass().addClass("indicator blue")
$(".page-card-head .reset-password-heading")
.html(__("Status Updated")); .html(__("Status Updated"));
if(r.message) { if(r.message) {
frappe.msgprint({ frappe.msgprint({
@@ -132,7 +137,7 @@ frappe.ready(function() {
}, },
statusCode: { statusCode: {
401: function() { 401: function() {
$('.page-card-head .indicator').removeClass().addClass('indicator red')
$('.page-card-head .reset-password-heading')
.text("{{ _('Invalid Password') }}"); .text("{{ _('Invalid Password') }}");
}, },
200: function(r) { 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_message.html(message.join(' ') || '').removeClass('hidden');
// strength_indicator.attr('title', message.join(' ') || ''); // strength_indicator.attr('title', message.join(' ') || '');
} }


+ 1
- 2
requirements.txt Bestand weergeven

@@ -24,8 +24,7 @@ googlemaps~=4.4.5
gunicorn~=20.1.0 gunicorn~=20.1.0
html2text==2020.1.16 html2text==2020.1.16
html5lib~=1.1 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 Jinja2~=3.0.1
ldap3~=2.9 ldap3~=2.9
markdown2~=2.4.0 markdown2~=2.4.0


+ 1
- 1
setup.py Bestand weergeven

@@ -57,5 +57,5 @@ setup(
{ {
'clean': CleanCommand 'clean': CleanCommand
}, },
python_requires='>=3.6'
python_requires='>=3.7'
) )

Laden…
Annuleren
Opslaan