浏览代码

Merge branch 'develop' of https://github.com/frappe/frappe into fix-blog-post-pagination

version-14
Aditya Hase 4 年前
父节点
当前提交
dce13faa52
找不到此签名对应的密钥 GPG 密钥 ID: A55F0FCA0234972
共有 26 个文件被更改,包括 289 次插入697 次删除
  1. +21
    -15
      .github/frappe_linter/translation.py
  2. +2
    -2
      cypress/integration/recorder.js
  3. +2
    -2
      frappe/__init__.py
  4. +5
    -1
      frappe/commands/utils.py
  5. +49
    -596
      frappe/core/doctype/activity_log/activity_log.json
  6. +0
    -3
      frappe/core/doctype/activity_log/activity_log.py
  7. +21
    -20
      frappe/core/doctype/file/file.py
  8. +1
    -0
      frappe/custom/doctype/package_publish_tool/package_publish_tool.py
  9. +1
    -1
      frappe/desk/desktop.py
  10. +2
    -2
      frappe/desk/doctype/dashboard_chart/dashboard_chart.py
  11. +1
    -1
      frappe/desk/doctype/desk_page/desk_page.py
  12. +5
    -3
      frappe/event_streaming/doctype/event_consumer/event_consumer.json
  13. +19
    -11
      frappe/event_streaming/doctype/event_consumer/event_consumer.py
  14. +7
    -4
      frappe/event_streaming/doctype/event_producer/event_producer.json
  15. +32
    -9
      frappe/event_streaming/doctype/event_producer/event_producer.py
  16. +34
    -14
      frappe/event_streaming/doctype/event_producer/test_event_producer.py
  17. +0
    -1
      frappe/model/rename_doc.py
  18. +1
    -0
      frappe/patches.txt
  19. +11
    -0
      frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py
  20. +10
    -5
      frappe/public/js/frappe/views/reports/query_report.js
  21. +1
    -1
      frappe/public/less/report.less
  22. +46
    -0
      frappe/tests/test_commands.py
  23. +2
    -1
      frappe/utils/__init__.py
  24. +2
    -2
      frappe/website/doctype/blog_post/blog_post.js
  25. +8
    -2
      frappe/website/doctype/blog_post/blog_post.json
  26. +6
    -1
      frappe/website/doctype/blog_post/blog_post.py

+ 21
- 15
.github/frappe_linter/translation.py 查看文件

@@ -7,22 +7,28 @@ start_pattern = re.compile(r"_{1,2}\([\"']{1,3}")


# skip first argument # skip first argument
files = sys.argv[1:] files = sys.argv[1:]
for _file in files:
if not _file.endswith(('.py', '.js')):
continue
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
for _file in files_to_scan:
with open(_file, 'r') as f: with open(_file, 'r') as f:
print(f'Checking: {_file}') print(f'Checking: {_file}')
for num, line in enumerate(f, 1):
all_matches = start_pattern.finditer(line)
if all_matches:
for match in all_matches:
verify = pattern.search(line)
if not verify:
errors_encounter += 1
print(f'A syntax error has been discovered at line number: {num}')
print(f'Syntax error occurred with: {line}')
file_lines = f.readlines()
for line_number, line in enumerate(file_lines, 1):
start_matches = start_pattern.search(line)
if start_matches:
match = pattern.search(line)
if not match and line.endswith(',\n'):
# concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:])
line = line[start_matches.start() + 1:]
match = pattern.match(line)

if not match:
errors_encounter += 1
print(f'\nTranslation syntax error at line number: {line_number + 1}\n{line.strip()[:100]}')

if errors_encounter > 0: if errors_encounter > 0:
print('You can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.')
assert 1+1 == 3
print('\nYou can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.')
sys.exit(1)
else: else:
print('Good To Go!')
print('\nGood To Go!')

+ 2
- 2
cypress/integration/recorder.js 查看文件

@@ -61,10 +61,10 @@ context('Recorder', () => {


cy.visit('/desk#recorder'); cy.visit('/desk#recorder');


cy.get('.list-row-container span').contains('frappe.desk.reportview.get').click();
cy.get('.list-row-container span').contains('/api/method/frappe').click();


cy.location('hash').should('contain', '#recorder/request/'); cy.location('hash').should('contain', '#recorder/request/');
cy.get('form').should('contain', 'frappe.desk.reportview.get');
cy.get('form').should('contain', '/api/method/frappe');


cy.get('#page-recorder .primary-action').should('contain', 'Stop').click(); cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click(); cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();


+ 2
- 2
frappe/__init__.py 查看文件

@@ -1110,8 +1110,8 @@ def get_newargs(fn, kwargs):
if (a in fnargs) or varkw: if (a in fnargs) or varkw:
newargs[a] = kwargs.get(a) newargs[a] = kwargs.get(a)


if "flags" in newargs:
del newargs["flags"]
newargs.pop("ignore_permissions", None)
newargs.pop("flags", None)


return newargs return newargs




+ 5
- 1
frappe/commands/utils.py 查看文件

@@ -133,6 +133,7 @@ def reset_perms(context):
def execute(context, method, args=None, kwargs=None, profile=False): def execute(context, method, args=None, kwargs=None, profile=False):
"Execute a function" "Execute a function"
for site in context.sites: for site in context.sites:
ret = ""
try: try:
frappe.init(site=site) frappe.init(site=site)
frappe.connect() frappe.connect()
@@ -154,7 +155,10 @@ def execute(context, method, args=None, kwargs=None, profile=False):
pr = cProfile.Profile() pr = cProfile.Profile()
pr.enable() pr.enable()


ret = frappe.get_attr(method)(*args, **kwargs)
try:
ret = frappe.get_attr(method)(*args, **kwargs)
except Exception:
ret = frappe.safe_eval(method + "(*args, **kwargs)", eval_globals=globals(), eval_locals=locals())


if profile: if profile:
pr.disable() pr.disable()


+ 49
- 596
frappe/core/doctype/activity_log/activity_log.json 查看文件

@@ -1,731 +1,184 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
{
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2017-10-05 11:10:38.780133", "creation": "2017-10-05 11:10:38.780133",
"custom": 0,
"description": "Keep track of all update feeds", "description": "Keep track of all update feeds",
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"subject",
"section_break_8",
"content",
"column_break_5",
"additional_info",
"communication_date",
"column_break_7",
"operation",
"status",
"reference_section",
"reference_doctype",
"reference_name",
"reference_owner",
"column_break_14",
"timeline_doctype",
"timeline_name",
"link_doctype",
"link_name",
"user",
"full_name"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subject", "fieldname": "subject",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Subject", "label": "Subject",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_8", "fieldname": "section_break_8",
"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,
"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
"fieldtype": "Section Break"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "content", "fieldname": "content",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"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": "Message", "label": "Message",
"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,
"width": "400" "width": "400"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5", "fieldname": "column_break_5",
"fieldtype": "Column 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,
"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
"fieldtype": "Column Break"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1, "collapsible": 1,
"columns": 0,
"fieldname": "additional_info", "fieldname": "additional_info",
"fieldtype": "Section Break", "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": "More Information",
"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
"label": "More Information"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Now", "default": "Now",
"fieldname": "communication_date", "fieldname": "communication_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"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": "Date",
"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
"label": "Date"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7", "fieldname": "column_break_7",
"fieldtype": "Column 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,
"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
"fieldtype": "Column Break"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "operation", "fieldname": "operation",
"fieldtype": "Select", "fieldtype": "Select",
"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": "Operation", "label": "Operation",
"length": 0,
"no_copy": 0,
"options": "\nLogin\nLogout",
"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
"options": "\nLogin\nLogout"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Status", "label": "Status",
"length": 0,
"no_copy": 0,
"options": "\nSuccess\nFailed\nLinked\nClosed",
"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
"options": "\nSuccess\nFailed\nLinked\nClosed"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1, "collapsible": 1,
"columns": 0,
"fieldname": "reference_section", "fieldname": "reference_section",
"fieldtype": "Section Break", "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": "Reference",
"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
"label": "Reference"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype", "fieldname": "reference_doctype",
"fieldtype": "Link", "fieldtype": "Link",
"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": "Reference Document Type", "label": "Reference Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"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
"options": "DocType"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"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": "Reference Name", "label": "Reference Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"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
"options": "reference_doctype"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "reference_name.owner", "fetch_from": "reference_name.owner",
"fieldname": "reference_owner", "fieldname": "reference_owner",
"fieldtype": "Read Only", "fieldtype": "Read Only",
"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": "Reference Owner", "label": "Reference Owner",
"length": 0,
"no_copy": 0,
"options": "",
"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
"search_index": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_14", "fieldname": "column_break_14",
"fieldtype": "Column 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,
"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
"fieldtype": "Column Break"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "timeline_doctype", "fieldname": "timeline_doctype",
"fieldtype": "Link", "fieldtype": "Link",
"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": "Timeline DocType", "label": "Timeline DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"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
"options": "DocType"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "timeline_name", "fieldname": "timeline_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"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": "Timeline Name", "label": "Timeline Name",
"length": 0,
"no_copy": 0,
"options": "timeline_doctype",
"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
"options": "timeline_doctype"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "link_doctype", "fieldname": "link_doctype",
"fieldtype": "Link", "fieldtype": "Link",
"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": "Link DocType", "label": "Link DocType",
"length": 0,
"no_copy": 0,
"options": "DocType", "options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "link_name", "fieldname": "link_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"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": "Link Name", "label": "Link Name",
"length": 0,
"no_copy": 0,
"options": "link_doctype", "options": "link_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "__user", "default": "__user",
"fieldname": "user", "fieldname": "user",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "User", "label": "User",
"length": 0,
"no_copy": 0,
"options": "User", "options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "full_name", "fieldname": "full_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Full Name",
"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
"label": "Full Name"
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-comment", "icon": "fa fa-comment",
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-09-05 14:22:27.664645",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-28 11:43:57.504565",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "Activity Log", "name": "Activity Log",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
"share": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 1,
"if_owner": 1,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 1,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "All", "role": "All",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
"share": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "subject", "search_fields": "subject",
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "subject", "title_field": "subject",


+ 0
- 3
frappe/core/doctype/activity_log/activity_log.py 查看文件

@@ -25,9 +25,6 @@ class ActivityLog(Document):
if self.reference_doctype and self.reference_name: if self.reference_doctype and self.reference_name:
self.status = "Linked" self.status = "Linked"


def on_trash(self): # pylint: disable=no-self-use
frappe.throw(_("Sorry! You cannot delete auto-generated comments"))

def on_doctype_update(): def on_doctype_update():
"""Add indexes in `tabActivity Log`""" """Add indexes in `tabActivity Log`"""
frappe.db.add_index("Activity Log", ["reference_doctype", "reference_name"]) frappe.db.add_index("Activity Log", ["reference_doctype", "reference_name"])


+ 21
- 20
frappe/core/doctype/file/file.py 查看文件

@@ -278,25 +278,26 @@ class File(Document):
base_url = os.path.dirname(self.file_url) base_url = os.path.dirname(self.file_url)


files = [] files = []
with zipfile.ZipFile(zip_path) as zf:
zf.extractall(os.path.dirname(zip_path))
for info in zf.infolist():
if not info.filename.startswith('__MACOSX'):
file_url = file_url = base_url + '/' + info.filename
file_name = frappe.db.get_value('File', dict(file_url=file_url))
if file_name:
file_doc = frappe.get_doc('File', file_name)
else:
file_doc = frappe.new_doc("File")
file_doc.file_name = info.filename
file_doc.file_size = info.file_size
file_doc.folder = self.folder
file_doc.is_private = self.is_private
file_doc.file_url = file_url
file_doc.attached_to_doctype = self.attached_to_doctype
file_doc.attached_to_name = self.attached_to_name
file_doc.save()
files.append(file_doc)
with zipfile.ZipFile(zip_path) as z:
for file in z.filelist:
if file.is_dir() or file.filename.startswith('__MACOSX/'):
# skip directories and macos hidden directory
continue

filename = os.path.basename(file.filename)
if filename.startswith('.'):
# skip hidden files
continue

file_doc = frappe.new_doc('File')
file_doc.content = z.read(file.filename)
file_doc.file_name = filename
file_doc.folder = self.folder
file_doc.is_private = self.is_private
file_doc.attached_to_doctype = self.attached_to_doctype
file_doc.attached_to_name = self.attached_to_name
file_doc.save()
files.append(file_doc)


frappe.delete_doc('File', self.name) frappe.delete_doc('File', self.name)
return files return files
@@ -941,7 +942,7 @@ def attach_files_to_document(doc, event):
# we dont want the update to fail if file cannot be attached for some reason # we dont want the update to fail if file cannot be attached for some reason
try: try:
value = doc.get(df.fieldname) value = doc.get(df.fieldname)
if not value.startswith(("/files", "/private/files")):
if not (value or '').startswith(("/files", "/private/files")):
return return


if frappe.db.exists("File", { if frappe.db.exists("File", {


+ 1
- 0
frappe/custom/doctype/package_publish_tool/package_publish_tool.py 查看文件

@@ -100,6 +100,7 @@ def export_package():
@frappe.whitelist() @frappe.whitelist()
def import_package(package=None): def import_package(package=None):
"""Import package from JSON.""" """Import package from JSON."""
frappe.only_for("System Manager")
if isinstance(package, string_types): if isinstance(package, string_types):
package = json.loads(package) package = json.loads(package)




+ 1
- 1
frappe/desk/desktop.py 查看文件

@@ -40,7 +40,7 @@ class Workspace:


self.doc = self.get_page_for_user() self.doc = self.get_page_for_user()


if self.doc.module not in self.allowed_modules:
if self.doc.module and self.doc.module not in self.allowed_modules:
raise frappe.PermissionError raise frappe.PermissionError


self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items) self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items)


+ 2
- 2
frappe/desk/doctype/dashboard_chart/dashboard_chart.py 查看文件

@@ -60,11 +60,11 @@ def has_permission(doc, ptype, user):




if doc.chart_type == 'Report': if doc.chart_type == 'Report':
allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()])
allowed_reports = [key if type(key) == str else key.encode('UTF8') for key in get_allowed_reports()]
if doc.report_name in allowed_reports: if doc.report_name in allowed_reports:
return True return True
else: else:
allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read())
allowed_doctypes = [frappe.permissions.get_doctypes_with_read()]
if doc.document_type in allowed_doctypes: if doc.document_type in allowed_doctypes:
return True return True




+ 1
- 1
frappe/desk/doctype/desk_page/desk_page.py 查看文件

@@ -38,7 +38,7 @@ class DeskPage(Document):


pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1) pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1)


return { page[1]: page[0] for page in pages }
return { page[1]: page[0] for page in pages if page[1] }


def disable_saving_as_standard(): def disable_saving_as_standard():
return frappe.flags.in_install or \ return frappe.flags.in_install or \


+ 5
- 3
frappe/event_streaming/doctype/event_consumer/event_consumer.json 查看文件

@@ -21,6 +21,7 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Callback URL", "label": "Callback URL",
"read_only": 1,
"reqd": 1, "reqd": 1,
"unique": 1 "unique": 1
}, },
@@ -28,19 +29,20 @@
"fieldname": "api_key", "fieldname": "api_key",
"fieldtype": "Data", "fieldtype": "Data",
"label": "API Key", "label": "API Key",
"read_only": 1
"reqd": 1
}, },
{ {
"fieldname": "api_secret", "fieldname": "api_secret",
"fieldtype": "Password", "fieldtype": "Password",
"label": "API Secret", "label": "API Secret",
"read_only": 1
"reqd": 1
}, },
{ {
"fieldname": "user", "fieldname": "user",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Event Subscriber", "label": "Event Subscriber",
"options": "User", "options": "User",
"read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -69,7 +71,7 @@
], ],
"in_create": 1, "in_create": 1,
"links": [], "links": [],
"modified": "2020-09-06 15:42:00.746493",
"modified": "2020-09-08 16:42:39.828085",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Event Streaming", "module": "Event Streaming",
"name": "Event Consumer", "name": "Event Consumer",


+ 19
- 11
frappe/event_streaming/doctype/event_consumer/event_consumer.py 查看文件

@@ -7,6 +7,7 @@ import frappe
import json import json
import requests import requests
import os import os
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.frappeclient import FrappeClient from frappe.frappeclient import FrappeClient
from frappe.utils.data import get_url from frappe.utils.data import get_url
@@ -23,6 +24,10 @@ class EventConsumer(Document):


def on_update(self): def on_update(self):
if not self.incoming_change: if not self.incoming_change:
doc_before_save = self.get_doc_before_save()
if doc_before_save.api_key != self.api_key or doc_before_save.api_secret != self.api_secret:
return

self.update_consumer_status() self.update_consumer_status()
else: else:
frappe.db.set_value(self.doctype, self.name, 'incoming_change', 0) frappe.db.set_value(self.doctype, self.name, 'incoming_change', 0)
@@ -58,17 +63,26 @@ class EventConsumer(Document):
return 'offline' return 'offline'
return 'online' return 'online'



@frappe.whitelist(allow_guest=True)
@frappe.whitelist()
def register_consumer(data): def register_consumer(data):
"""create an event consumer document for registering a consumer""" """create an event consumer document for registering a consumer"""
data = json.loads(data) data = json.loads(data)
# to ensure that consumer is created only once # to ensure that consumer is created only once
if frappe.db.exists('Event Consumer', data['event_consumer']): if frappe.db.exists('Event Consumer', data['event_consumer']):
return None return None

user = data['user']
if not frappe.db.exists('User', user):
frappe.throw(_('User {0} not found on the producer site').format(user))

if "System Manager" not in frappe.get_roles(user):
frappe.throw(_("Event Subscriber has to be a System Manager."))

consumer = frappe.new_doc('Event Consumer') consumer = frappe.new_doc('Event Consumer')
consumer.callback_url = data['event_consumer'] consumer.callback_url = data['event_consumer']
consumer.user = data['user'] consumer.user = data['user']
consumer.api_key = data['api_key']
consumer.api_secret = data['api_secret']
consumer.incoming_change = True consumer.incoming_change = True
consumer_doctypes = json.loads(data['consumer_doctypes']) consumer_doctypes = json.loads(data['consumer_doctypes'])


@@ -78,18 +92,13 @@ def register_consumer(data):
'status': 'Pending' 'status': 'Pending'
}) })


api_key = frappe.generate_hash(length=10)
api_secret = frappe.generate_hash(length=10)
consumer.api_key = api_key
consumer.api_secret = api_secret
consumer.insert(ignore_permissions=True)
frappe.db.commit()
consumer.insert()


# consumer's 'last_update' field should point to the latest update # consumer's 'last_update' field should point to the latest update
# in producer's update log when subscribing # in producer's update log when subscribing
# so that, updates after subscribing are consumed and not the old ones. # so that, updates after subscribing are consumed and not the old ones.
last_update = str(get_last_update()) last_update = str(get_last_update())
return json.dumps({'api_key': api_key, 'api_secret': api_secret, 'last_update': last_update})
return json.dumps({'last_update': last_update})




def get_consumer_site(consumer_url): def get_consumer_site(consumer_url):
@@ -98,8 +107,7 @@ def get_consumer_site(consumer_url):
consumer_site = FrappeClient( consumer_site = FrappeClient(
url=consumer_url, url=consumer_url,
api_key=consumer_doc.api_key, api_key=consumer_doc.api_key,
api_secret=consumer_doc.get_password('api_secret'),
frappe_authorization_source='Event Producer'
api_secret=consumer_doc.get_password('api_secret')
) )
return consumer_site return consumer_site




+ 7
- 4
frappe/event_streaming/doctype/event_producer/event_producer.json 查看文件

@@ -32,23 +32,26 @@
"read_only": 1 "read_only": 1
}, },
{ {
"description": "API Key of the user(Event Subscriber) on the producer site",
"fieldname": "api_key", "fieldname": "api_key",
"fieldtype": "Data", "fieldtype": "Data",
"label": "API Key", "label": "API Key",
"read_only": 1
"reqd": 1
}, },
{ {
"description": "API Secret of the user(Event Subscriber) on the producer site",
"fieldname": "api_secret", "fieldname": "api_secret",
"fieldtype": "Password", "fieldtype": "Password",
"label": "API Secret", "label": "API Secret",
"read_only": 1
"reqd": 1
}, },
{ {
"fieldname": "user", "fieldname": "user",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Event Subscriber", "label": "Event Subscriber",
"options": "User", "options": "User",
"reqd": 1
"reqd": 1,
"set_only_once": 1
}, },
{ {
"fieldname": "column_break_6", "fieldname": "column_break_6",
@@ -74,7 +77,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2019-12-26 13:04:11.438349",
"modified": "2020-09-08 18:50:57.687979",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Event Streaming", "module": "Event Streaming",
"name": "Event Producer", "name": "Event Producer",


+ 32
- 9
frappe/event_streaming/doctype/event_producer/event_producer.py 查看文件

@@ -12,7 +12,8 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.frappeclient import FrappeClient from frappe.frappeclient import FrappeClient
from frappe.utils.background_jobs import get_jobs from frappe.utils.background_jobs import get_jobs
from frappe.utils.data import get_url
from frappe.utils.data import get_url, get_link_to_form
from frappe.utils.password import get_decrypted_password
from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.integrations.oauth2 import validate_url from frappe.integrations.oauth2 import validate_url


@@ -20,19 +21,35 @@ from frappe.integrations.oauth2 import validate_url
class EventProducer(Document): class EventProducer(Document):
def before_insert(self): def before_insert(self):
self.check_url() self.check_url()
self.validate_event_subscriber()
self.incoming_change = True self.incoming_change = True
self.create_event_consumer() self.create_event_consumer()
self.create_custom_fields() self.create_custom_fields()


def validate(self): def validate(self):
self.validate_event_subscriber()
if frappe.flags.in_test: if frappe.flags.in_test:
for entry in self.producer_doctypes: for entry in self.producer_doctypes:
entry.status = 'Approved' entry.status = 'Approved'


def validate_event_subscriber(self):
if not frappe.db.get_value('User', self.user, 'api_key'):
frappe.throw(_('Please generate keys for the Event Subscriber User {0} first.').format(
frappe.bold(get_link_to_form('User', self.user))
))

def on_update(self): def on_update(self):
if not self.incoming_change: if not self.incoming_change:
self.update_event_consumer()
self.create_custom_fields()
if frappe.db.exists('Event Producer', self.name):
if not self.api_key or not self.api_secret:
frappe.throw(_('Please set API Key and Secret on the producer and consumer sites first.'))
else:
doc_before_save = self.get_doc_before_save()
if doc_before_save.api_key != self.api_key or doc_before_save.api_secret != self.api_secret:
return

self.update_event_consumer()
self.create_custom_fields()
else: else:
# when producer doc is updated it updates the consumer doc, set flag to avoid deadlock # when producer doc is updated it updates the consumer doc, set flag to avoid deadlock
self.db_set('incoming_change', 0) self.db_set('incoming_change', 0)
@@ -50,15 +67,18 @@ class EventProducer(Document):
def create_event_consumer(self): def create_event_consumer(self):
"""register event consumer on the producer site""" """register event consumer on the producer site"""
if self.is_producer_online(): if self.is_producer_online():
producer_site = FrappeClient(self.producer_url, verify=False)
producer_site = FrappeClient(
url=self.producer_url,
api_key=self.api_key,
api_secret=self.get_password('api_secret')
)

response = producer_site.post_api( response = producer_site.post_api(
'frappe.event_streaming.doctype.event_consumer.event_consumer.register_consumer', 'frappe.event_streaming.doctype.event_consumer.event_consumer.register_consumer',
params={'data': json.dumps(self.get_request_data())} params={'data': json.dumps(self.get_request_data())}
) )
if response: if response:
response = json.loads(response) response = json.loads(response)
self.api_key = response['api_key']
self.api_secret = response['api_secret']
self.last_update = response['last_update'] self.last_update = response['last_update']
else: else:
frappe.throw(_('Failed to create an Event Consumer or an Event Consumer for the current site is already registered.')) frappe.throw(_('Failed to create an Event Consumer or an Event Consumer for the current site is already registered.'))
@@ -72,10 +92,14 @@ class EventProducer(Document):
else: else:
consumer_doctypes.append(entry.ref_doctype) consumer_doctypes.append(entry.ref_doctype)


user_key = frappe.db.get_value('User', self.user, 'api_key')
user_secret = get_decrypted_password('User', self.user, 'api_secret')
return { return {
'event_consumer': get_url(), 'event_consumer': get_url(),
'consumer_doctypes': json.dumps(consumer_doctypes), 'consumer_doctypes': json.dumps(consumer_doctypes),
'user': self.user
'user': self.user,
'api_key': user_key,
'api_secret': user_secret
} }


def create_custom_fields(self): def create_custom_fields(self):
@@ -131,8 +155,7 @@ def get_producer_site(producer_url):
producer_site = FrappeClient( producer_site = FrappeClient(
url=producer_url, url=producer_url,
api_key=producer_doc.api_key, api_key=producer_doc.api_key,
api_secret=producer_doc.get_password('api_secret'),
frappe_authorization_source='Event Consumer'
api_secret=producer_doc.get_password('api_secret')
) )
return producer_site return producer_site




+ 34
- 14
frappe/event_streaming/doctype/event_producer/test_event_producer.py 查看文件

@@ -8,6 +8,7 @@ import unittest
import json import json
from frappe.frappeclient import FrappeClient from frappe.frappeclient import FrappeClient
from frappe.event_streaming.doctype.event_producer.event_producer import pull_from_node from frappe.event_streaming.doctype.event_producer.event_producer import pull_from_node
from frappe.core.doctype.user.user import generate_keys


producer_url = 'http://test_site_producer:8000' producer_url = 'http://test_site_producer:8000'


@@ -166,16 +167,6 @@ class TestEventProducer(unittest.TestCase):
def pull_producer_data(self): def pull_producer_data(self):
pull_from_node(producer_url) pull_from_node(producer_url)


def get_remote_site(self):
producer_doc = frappe.get_doc('Event Producer', producer_url)
producer_site = FrappeClient(
url=producer_doc.producer_url,
api_key=producer_doc.api_key,
api_secret=producer_doc.get_password('api_secret'),
frappe_authorization_source='Event Consumer'
)
return producer_site

def test_mapping(self): def test_mapping(self):
producer = get_remote_site() producer = get_remote_site()
event_producer = frappe.get_doc('Event Producer', producer_url, for_update=True) event_producer = frappe.get_doc('Event Producer', producer_url, for_update=True)
@@ -298,6 +289,20 @@ def create_event_producer(producer_url):
event_producer.save() event_producer.save()
return return


generate_keys('Administrator')

producer_site = connect()

response = producer_site.post_api(
'frappe.core.doctype.user.user.generate_keys',
params={'user': 'Administrator'}
)

api_secret = response.get('api_secret')

response = producer_site.get_value('User', 'api_key', {'name': 'Administrator'})
api_key = response.get('api_key')

event_producer = frappe.new_doc('Event Producer') event_producer = frappe.new_doc('Event Producer')
event_producer.producer_doctypes = [] event_producer.producer_doctypes = []
event_producer.producer_url = producer_url event_producer.producer_url = producer_url
@@ -310,6 +315,8 @@ def create_event_producer(producer_url):
'use_same_name': 1 'use_same_name': 1
}) })
event_producer.user = 'Administrator' event_producer.user = 'Administrator'
event_producer.api_key = api_key
event_producer.api_secret = api_secret
event_producer.save() event_producer.save()


def reset_configuration(producer_url): def reset_configuration(producer_url):
@@ -331,9 +338,9 @@ def get_remote_site():
producer_doc = frappe.get_doc('Event Producer', producer_url) producer_doc = frappe.get_doc('Event Producer', producer_url)
producer_site = FrappeClient( producer_site = FrappeClient(
url=producer_doc.producer_url, url=producer_doc.producer_url,
api_key=producer_doc.api_key,
api_secret=producer_doc.get_password('api_secret'),
frappe_authorization_source='Event Consumer'
username='Administrator',
password='admin',
verify=False
) )
return producer_site return producer_site


@@ -341,4 +348,17 @@ def unsubscribe_doctypes(producer_url):
event_producer = frappe.get_doc('Event Producer', producer_url) event_producer = frappe.get_doc('Event Producer', producer_url)
for entry in event_producer.producer_doctypes: for entry in event_producer.producer_doctypes:
entry.unsubscribe = 1 entry.unsubscribe = 1
event_producer.save()
event_producer.save()

def connect():
def _connect():
return FrappeClient(
url=producer_url,
username='Administrator',
password='admin',
verify=False
)
try:
return _connect()
except Exception:
return _connect()

+ 0
- 1
frappe/model/rename_doc.py 查看文件

@@ -25,7 +25,6 @@ def update_document_title(doctype, docname, title_field=None, old_title=None, ne


return docname return docname


@frappe.whitelist()
def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False, show_alert=True): def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False, show_alert=True):
""" """
Renames a doc(dt, old) to doc(dt, new) and Renames a doc(dt, old) to doc(dt, new) and


+ 1
- 0
frappe/patches.txt 查看文件

@@ -308,3 +308,4 @@ frappe.patches.v13_0.remove_duplicate_navbar_items
frappe.patches.v13_0.set_route_for_blog_category frappe.patches.v13_0.set_route_for_blog_category
frappe.patches.v13_0.enable_custom_script frappe.patches.v13_0.enable_custom_script
frappe.patches.v13_0.update_newsletter_content_type frappe.patches.v13_0.update_newsletter_content_type
frappe.patches.v13_0.delete_event_producer_and_consumer_keys

+ 11
- 0
frappe/patches/v13_0/delete_event_producer_and_consumer_keys.py 查看文件

@@ -0,0 +1,11 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

from __future__ import unicode_literals
import frappe

def execute():
if frappe.db.exists("DocType", "Event Producer"):
frappe.db.sql("""UPDATE `tabEvent Producer` SET api_key='', api_secret=''""")
if frappe.db.exists("DocType", "Event Consumer"):
frappe.db.sql("""UPDATE `tabEvent Consumer` SET api_key='', api_secret=''""")

+ 10
- 5
frappe/public/js/frappe/views/reports/query_report.js 查看文件

@@ -1259,7 +1259,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
return; return;
} }


this.export_dialog = frappe.prompt([
let export_dialog_fields = [
{ {
label: __('Select File Format'), label: __('Select File Format'),
fieldname: 'file_format', fieldname: 'file_format',
@@ -1267,13 +1267,18 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
options: ['Excel', 'CSV'], options: ['Excel', 'CSV'],
default: 'Excel', default: 'Excel',
reqd: 1 reqd: 1
},
{
}
];

if (this.tree_report) {
export_dialog_fields.push({
label: __("Include indentation"), label: __("Include indentation"),
fieldname: "include_indentation", fieldname: "include_indentation",
fieldtype: "Check", fieldtype: "Check",
}
], ({ file_format, include_indentation }) => {
});
}

this.export_dialog = frappe.prompt(export_dialog_fields, ({ file_format, include_indentation }) => {
this.make_access_log('Export', file_format); this.make_access_log('Export', file_format);
if (file_format === 'CSV') { if (file_format === 'CSV') {
const column_row = this.columns.reduce((acc, col) => { const column_row = this.columns.reduce((acc, col) => {


+ 1
- 1
frappe/public/less/report.less 查看文件

@@ -72,7 +72,7 @@
margin-bottom: 10px; margin-bottom: 10px;
} }


.report-wrapper {
.report-wrapper, .datatable-wrapper {
overflow: auto; overflow: auto;
} }




+ 46
- 0
frappe/tests/test_commands.py 查看文件

@@ -0,0 +1,46 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors

# imports - standard imports
import shlex
import subprocess
import unittest

# imports - module imports
import frappe


def clean(value):
if isinstance(value, (bytes, str)):
value = value.decode().strip()
return value


class BaseTestCommands:
def execute(self, command):
command = command.format(**{"site": frappe.local.site})
command = shlex.split(command)
self._proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.stdout = clean(self._proc.stdout)
self.stderr = clean(self._proc.stderr)
self.returncode = clean(self._proc.returncode)


class TestCommands(BaseTestCommands, unittest.TestCase):
def test_execute(self):
# test 1: execute a command expecting a numeric output
self.execute("bench --site {site} execute frappe.db.get_database_size")
self.assertEquals(self.returncode, 0)
self.assertIsInstance(float(self.stdout), float)

# test 2: execute a command expecting an errored output as local won't exist
self.execute("bench --site {site} execute frappe.local.site")
self.assertEquals(self.returncode, 1)
self.assertIsNotNone(self.stderr)

# test 3: execute a command with kwargs
# Note:
# terminal command has been escaped to avoid .format string replacement
# The returned value has quotes which have been trimmed for the test
self.execute("""bench --site {site} execute frappe.bold --kwargs '{{"text": "DocType"}}'""")
self.assertEquals(self.returncode, 0)
self.assertEquals(self.stdout[1:-1], frappe.bold(text='DocType'))

+ 2
- 1
frappe/utils/__init__.py 查看文件

@@ -135,7 +135,8 @@ def validate_email_address(email_str, throw=False):


if not _valid: if not _valid:
if throw: if throw:
frappe.throw(frappe._("{0} is not a valid Email Address").format(e),
invalid_email = frappe.utils.escape_html(e)
frappe.throw(frappe._("{0} is not a valid Email Address").format(invalid_email),
frappe.InvalidEmailAddressError) frappe.InvalidEmailAddressError)
return None return None
else: else:


+ 2
- 2
frappe/website/doctype/blog_post/blog_post.js 查看文件

@@ -33,9 +33,9 @@ frappe.ui.form.on('Blog Post', {
}); });


function generate_google_search_preview(frm) { function generate_google_search_preview(frm) {
if (!frm.doc.title) return;
if (!(frm.doc.meta_title || frm.doc.title)) return;
let google_preview = frm.get_field("google_preview"); let google_preview = frm.get_field("google_preview");
let seo_title = (frm.doc.title).slice(0, 60);
let seo_title = (frm.doc.meta_title || frm.doc.title).slice(0, 60);
let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160); let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160);
let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : ''; let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : '';
let route_array = frm.doc.route ? frm.doc.route.split('/') : []; let route_array = frm.doc.route ? frm.doc.route.split('/') : [];


+ 8
- 2
frappe/website/doctype/blog_post/blog_post.json 查看文件

@@ -26,6 +26,7 @@
"content_html", "content_html",
"email_sent", "email_sent",
"meta_tags", "meta_tags",
"meta_title",
"meta_description", "meta_description",
"column_break_18", "column_break_18",
"meta_image", "meta_image",
@@ -110,7 +111,6 @@
"depends_on": "eval:doc.content_type === 'Markdown'", "depends_on": "eval:doc.content_type === 'Markdown'",
"fieldname": "content_md", "fieldname": "content_md",
"fieldtype": "Markdown Editor", "fieldtype": "Markdown Editor",
"ignore_xss_filter": 1,
"label": "Content (Markdown)" "label": "Content (Markdown)"
}, },
{ {
@@ -185,6 +185,12 @@
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1, "hidden": 1,
"label": "Hide CTA" "label": "Hide CTA"
},
{
"fieldname": "meta_title",
"fieldtype": "Data",
"label": "Meta Title",
"length": 60
} }
], ],
"has_web_view": 1, "has_web_view": 1,
@@ -194,7 +200,7 @@
"is_published_field": "published", "is_published_field": "published",
"links": [], "links": [],
"max_attachments": 5, "max_attachments": 5,
"modified": "2020-08-31 16:55:03.687862",
"modified": "2020-08-31 21:01:51.100349",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Website", "module": "Website",
"name": "Blog Post", "name": "Blog Post",


+ 6
- 1
frappe/website/doctype/blog_post/blog_post.py 查看文件

@@ -36,6 +36,11 @@ class BlogPost(WebsiteGenerator):
if self.blog_intro: if self.blog_intro:
self.blog_intro = self.blog_intro[:200] self.blog_intro = self.blog_intro[:200]


if not self.meta_title:
self.meta_title = self.title[:60]
else:
self.meta_title = self.meta_title[:60]

if not self.meta_description: if not self.meta_description:
self.meta_description = self.blog_intro[:140] self.meta_description = self.blog_intro[:140]
else: else:
@@ -88,7 +93,7 @@ class BlogPost(WebsiteGenerator):
context.description = self.meta_description or self.blog_intro or strip_html_tags(context.content[:140]) context.description = self.meta_description or self.blog_intro or strip_html_tags(context.content[:140])


context.metatags = { context.metatags = {
"name": self.title,
"name": self.meta_title,
"description": context.description, "description": context.description,
} }




正在加载...
取消
保存