Преглед на файлове

Merge branch 'develop' into section_with_tab_subtitle

version-14
Suraj Shetty преди 4 години
committed by GitHub
родител
ревизия
c1579aa405
No known key found for this signature in database GPG ключ ID: 4AEE18F83AFDEB23
променени са 70 файла, в които са добавени 2751 реда и са изтрити 1317 реда
  1. +1
    -1
      .github/ISSUE_TEMPLATE/bug_report.md
  2. +1
    -1
      .github/ISSUE_TEMPLATE/feature_request.md
  3. +1
    -1
      .github/ISSUE_TEMPLATE/question-about-using-frappe.md
  4. +4
    -3
      .github/frappe-framework-logo.svg
  5. +1
    -1
      .github/frappe_linter/translation.py
  6. +3
    -1
      .snyk
  7. +1
    -1
      README.md
  8. +1
    -1
      cypress/integration/recorder.js
  9. +8
    -1
      frappe/client.py
  10. +1
    -1
      frappe/contacts/doctype/contact/contact.py
  11. +7
    -0
      frappe/core/doctype/data_import/importer.py
  12. +3
    -1
      frappe/core/doctype/docfield/docfield.json
  13. +2
    -1
      frappe/core/doctype/doctype/doctype.py
  14. +37
    -0
      frappe/core/doctype/file/file.py
  15. +46
    -0
      frappe/core/doctype/file/test_file.py
  16. +3
    -9
      frappe/core/doctype/user/user.json
  17. +3
    -2
      frappe/core/page/permission_manager/permission_manager.js
  18. +273
    -271
      frappe/custom/doctype/custom_field/custom_field.json
  19. +78
    -174
      frappe/custom/doctype/custom_script/custom_script.json
  20. +257
    -255
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  21. +2
    -0
      frappe/database/mariadb/setup_db.py
  22. +14
    -1
      frappe/desk/desktop.py
  23. +1
    -1
      frappe/desk/form/meta.py
  24. +5
    -3
      frappe/desk/link_preview.py
  25. +32
    -4
      frappe/desk/query_report.py
  26. +0
    -3
      frappe/email/doctype/newsletter/newsletter.js
  27. +39
    -9
      frappe/email/doctype/newsletter/newsletter.json
  28. +19
    -21
      frappe/email/doctype/newsletter/newsletter.py
  29. +4
    -2
      frappe/email/doctype/newsletter/newsletter_list.js
  30. +1
    -0
      frappe/email/doctype/newsletter/test_newsletter.py
  31. +2
    -1
      frappe/hooks.py
  32. +4
    -1
      frappe/installer.py
  33. +8
    -8
      frappe/integrations/desk_page/integrations/integrations.json
  34. +16
    -5
      frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py
  35. +5
    -1
      frappe/integrations/doctype/twilio_number_group/twilio_number_group.json
  36. +10
    -1
      frappe/integrations/doctype/twilio_settings/twilio_settings.py
  37. +10
    -0
      frappe/integrations/offsite_backup_utils.py
  38. +6
    -1
      frappe/model/base_document.py
  39. +6
    -0
      frappe/model/utils/rename_field.py
  40. +8
    -2
      frappe/patches.txt
  41. +44
    -0
      frappe/patches/v12_0/fix_email_id_formatting.py
  42. +2
    -0
      frappe/patches/v12_0/remove_deprecated_fields_from_doctype.py
  43. +3
    -0
      frappe/patches/v13_0/add_toggle_width_in_navbar_settings.py
  44. +13
    -0
      frappe/patches/v13_0/enable_custom_script.py
  45. +14
    -0
      frappe/patches/v13_0/remove_duplicate_navbar_items.py
  46. +12
    -0
      frappe/patches/v13_0/update_newsletter_content_type.py
  47. +6
    -3
      frappe/printing/doctype/print_format/print_format.json
  48. +7
    -0
      frappe/public/js/frappe/file_uploader/FileUploader.vue
  49. +2
    -0
      frappe/public/js/frappe/file_uploader/index.js
  50. +1
    -0
      frappe/public/js/frappe/form/controls/attach.js
  51. +4
    -1
      frappe/public/js/frappe/form/controls/text_editor.js
  52. +1
    -1
      frappe/public/js/frappe/form/layout.js
  53. +1
    -1
      frappe/public/js/frappe/ui/toolbar/about.js
  54. +3
    -1
      frappe/public/js/frappe/views/reports/query_report.js
  55. +1
    -1
      frappe/public/js/frappe/widgets/utils.js
  56. +1
    -1
      frappe/templates/base.html
  57. +1
    -1
      frappe/templates/includes/footer/footer_powered.html
  58. +10
    -0
      frappe/tests/test_api.py
  59. +9
    -2
      frappe/tests/test_safe_exec.py
  60. +0
    -2
      frappe/twofactor.py
  61. +27
    -15
      frappe/utils/backups.py
  62. +4
    -0
      frappe/utils/file_manager.py
  63. +11
    -1
      frappe/utils/safe_exec.py
  64. +2
    -1
      frappe/website/doctype/blog_post/blog_post.json
  65. +1
    -1
      frappe/website/doctype/web_form/web_form.json
  66. +2
    -1
      frappe/website/doctype/web_page/web_page.json
  67. +2
    -2
      frappe/website/render.py
  68. +3
    -2
      package.json
  69. +1
    -1
      requirements.txt
  70. +1640
    -490
      yarn.lock

+ 1
- 1
.github/ISSUE_TEMPLATE/bug_report.md Целия файл

@@ -9,7 +9,7 @@ Welcome to the Frappe Framework issue tracker! Before creating an issue, please

1. This tracker should only be used to report bugs and request features / enhancements to Frappe
- For questions and general support, use https://stackoverflow.com/questions/tagged/frappe
- For documentation issues, refer to https://frappe.io/docs/user/en or the developer cheetsheet https://github.com/frappe/frappe/wiki/Developer-Cheatsheet
- For documentation issues, refer to https://frappeframework.com/docs/user/en or the developer cheetsheet https://github.com/frappe/frappe/wiki/Developer-Cheatsheet
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
the original discussion.
3. When making a bug report, make sure you provide all required information. The easier it is for


+ 1
- 1
.github/ISSUE_TEMPLATE/feature_request.md Целия файл

@@ -9,7 +9,7 @@ Welcome to the Frappe Framework issue tracker! Before creating an issue, please

1. This tracker should only be used to report bugs and request features / enhancements to Frappe
- For questions and general support, refer to https://stackoverflow.com/questions/tagged/frappe
- For documentation issues, use https://frappe.io/docs/user/en or the developer cheetsheet https://github.com/frappe/frappe/wiki/Developer-Cheatsheet
- For documentation issues, use https://frappeframework.com/docs/user/en or the developer cheetsheet https://frappeframework.com/docs/user/en/bench/resources/bench-commands-cheatsheet
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
the original discussion.
3. When making a feature request, make sure to be as verbose as possible. The better you convey your message, the greater the drive to make it happen.


+ 1
- 1
.github/ISSUE_TEMPLATE/question-about-using-frappe.md Целия файл

@@ -12,7 +12,7 @@ for questions about using `ERPNext`: https://discuss.erpnext.com

for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)

For documentation issues, use the [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
For documentation issues, use the [Frappe Framework Documentation](https://frappeframework.com/docs) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)

For a slightly outdated yet informative developer guide: https://www.youtube.com/playlist?list=PL3lFfCEoMxvzHtsZHFJ4T3n5yMM3nGJ1W


+ 4
- 3
.github/frappe-framework-logo.svg
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
.github/frappe_linter/translation.py Целия файл

@@ -22,7 +22,7 @@ for _file in files:
print(f'A syntax error has been discovered at line number: {num}')
print(f'Syntax error occurred with: {line}')
if errors_encounter > 0:
print('You can visit "https://frappe.io/docs/user/en/translations" to resolve this error.')
print('You can visit "https://frappeframework.com/docs/user/en/translations" to resolve this error.')
assert 1+1 == 3
else:
print('Good To Go!')

+ 3
- 1
.snyk Целия файл

@@ -1,5 +1,5 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.1
version: v1.19.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
SNYK-JS-AWESOMPLETE-174474:
@@ -63,3 +63,5 @@ patch:
patched: '2020-04-30T23:02:32.330Z'
- snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash:
patched: '2020-04-30T23:02:32.330Z'
- quill-image-resize > lodash:
patched: '2020-08-24T23:06:37.710Z'

+ 1
- 1
README.md Целия файл

@@ -17,7 +17,7 @@
<a href="https://travis-ci.com/frappe/frappe">
<img src="https://travis-ci.com/frappe/frappe.svg?branch=develop">
</a>
<a href='https://frappe.io/docs'>
<a href='https://frappeframework.com/docs'>
<img src='https://img.shields.io/badge/docs-📖-7575FF.svg?style=flat-square'/>
</a>
<a href='https://www.codetriage.com/frappe/frappe'>


+ 1
- 1
cypress/integration/recorder.js Целия файл

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

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

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

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


+ 8
- 1
frappe/client.py Целия файл

@@ -73,6 +73,8 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
frappe.throw(_("No permission for {0}").format(doctype), frappe.PermissionError)

filters = get_safe_filters(filters)
if isinstance(filters, string_types):
filters = {"name": filters}

try:
fields = json.loads(fieldname)
@@ -85,7 +87,12 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
if not filters:
filters = None

value = frappe.get_list(doctype, filters=filters, fields=fields, debug=debug, limit=1)

if frappe.get_meta(doctype).issingle:
value = frappe.db.get_values_from_single(fields, filters, doctype, as_dict=as_dict, debug=debug)
else:
value = frappe.get_list(doctype, filters=filters, fields=fields, debug=debug, limit=1)

if as_dict:
value = value[0] if value else {}
else:


+ 1
- 1
frappe/contacts/doctype/contact/contact.py Целия файл

@@ -188,7 +188,7 @@ def contact_query(doctype, txt, searchfield, start, page_len, filters):
from frappe.desk.reportview import get_match_cond

if not frappe.get_meta("Contact").get_field(searchfield)\
or searchfield not in frappe.db.DEFAULT_COLUMNS:
and searchfield not in frappe.db.DEFAULT_COLUMNS:
return []

link_doctype = filters.pop('link_doctype')


+ 7
- 0
frappe/core/doctype/data_import/importer.py Целия файл

@@ -465,6 +465,8 @@ class ImportFile:

if doctype != self.doctype and table_df:
child_doc = row.parse_doc(doctype, parent_doc, table_df)
if child_doc is None:
continue
parent_doc[table_df.fieldname] = parent_doc.get(table_df.fieldname, [])
parent_doc[table_df.fieldname].append(child_doc)

@@ -570,6 +572,11 @@ class Row:
def parse_doc(self, doctype, parent_doc=None, table_df=None):
col_indexes = self.header.get_column_indexes(doctype, table_df)
values = self.get_values(col_indexes)

if all(v in INVALID_VALUES for v in values):
# if all values are invalid, no need to parse it
return None

columns = self.header.get_columns(col_indexes)
doc = self._parse_doc(doctype, columns, values, parent_doc, table_df)
return doc


+ 3
- 1
frappe/core/doctype/docfield/docfield.json Целия файл

@@ -163,6 +163,7 @@
},
{
"default": "0",
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
@@ -475,9 +476,10 @@
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-02-06 09:06:25.224413",
"modified": "2020-08-28 11:28:21.252853",
"modified_by": "Administrator",
"module": "Core",
"name": "DocField",


+ 2
- 1
frappe/core/doctype/doctype/doctype.py Целия файл

@@ -989,7 +989,8 @@ def clear_permissions_cache(doctype):
`tabHas Role`,
`tabDocPerm`
WHERE `tabDocPerm`.`parent` = %s
AND `tabDocPerm`.`role` = `tabHas Role`.`role`
AND `tabDocPerm`.`role` = `tabHas Role`.`role`
AND `tabHas Role`.`parenttype` = 'User'
""", doctype):
frappe.clear_cache(user=user)



+ 37
- 0
frappe/core/doctype/file/file.py Целия файл

@@ -922,3 +922,40 @@ def update_existing_file_docs(doc):
content_hash=doc.content_hash,
file_name=doc.name
))

def attach_files_to_document(doc, event):
""" Runs on on_update hook of all documents.
Goes through every Attach and Attach Image field and attaches
the file url to the document if it is not already attached.
"""

attach_fields = doc.meta.get(
"fields", {"fieldtype": ["in", ["Attach", "Attach Image"]]}
)

for df in attach_fields:
# this method runs in on_update hook of all documents
# we dont want the update to fail if file cannot be attached for some reason
try:
value = doc.get(df.fieldname)
if not value.startswith(("/files", "/private/files")):
return

if frappe.db.exists("File", {
"file_url": value,
"attached_to_name": doc.name,
"attached_to_doctype": doc.doctype,
"attached_to_field": df.fieldname,
}):
return

frappe.get_doc(
doctype="File",
file_url=value,
attached_to_name=doc.name,
attached_to_doctype=doc.doctype,
attached_to_field=df.fieldname,
folder="Home/Attachments",
).insert()
except Exception:
frappe.log_error(title=_("Error Attaching File"))

+ 46
- 0
frappe/core/doctype/file/test_file.py Целия файл

@@ -328,3 +328,49 @@ class TestFile(unittest.TestCase):
self.assertTrue(os.path.exists(file2.get_full_path()))


class TestAttachment(unittest.TestCase):
test_doctype = 'Test For Attachment'

def setUp(self):
if frappe.db.exists('DocType', self.test_doctype):
return

frappe.get_doc(
doctype='DocType',
name=self.test_doctype,
module='Custom',
custom=1,
fields=[
{'label': 'Title', 'fieldname': 'title', 'fieldtype': 'Data'},
{'label': 'Attachment', 'fieldname': 'attachment', 'fieldtype': 'Attach'},
]
).insert()

def tearDown(self):
frappe.delete_doc('DocType', self.test_doctype)

def test_file_attachment_on_update(self):
doc = frappe.get_doc(
doctype=self.test_doctype,
title='test for attachment on update'
).insert()

file = frappe.get_doc({
'doctype': 'File',
'file_name': 'test_attach.txt',
'content': 'Test Content'
})
file.save()

doc.attachment = file.file_url
doc.save()

exists = frappe.db.exists('File', {
'file_name': 'test_attach.txt',
'file_url': file.file_url,
'attached_to_doctype': self.test_doctype,
'attached_to_name': doc.name,
'attached_to_field': 'attachment'
})

self.assertTrue(exists)

+ 3
- 9
frappe/core/doctype/user/user.json Целия файл

@@ -356,7 +356,7 @@
"depends_on": "enabled",
"fieldname": "email_settings",
"fieldtype": "Section Break",
"label": "Email Settings"
"label": "Email"
},
{
"default": "1",
@@ -382,12 +382,6 @@
"label": "Email Signature",
"no_copy": 1
},
{
"collapsible": 1,
"fieldname": "email_inbox",
"fieldtype": "Section Break",
"label": "Email Inbox"
},
{
"fieldname": "user_emails",
"fieldtype": "Table",
@@ -651,7 +645,7 @@
}
],
"max_attachments": 5,
"modified": "2020-08-06 19:48:49.677800",
"modified": "2020-08-26 19:48:49.677800",
"modified_by": "Administrator",
"module": "Core",
"name": "User",
@@ -685,4 +679,4 @@
"sort_order": "DESC",
"title_field": "full_name",
"track_changes": 1
}
}

+ 3
- 2
frappe/core/page/permission_manager/permission_manager.js Целия файл

@@ -334,6 +334,7 @@ frappe.PermissionEngine = Class.extend({
});

this.body.on("click", "input[type='checkbox']", function() {
frappe.dom.freeze();
var chk = $(this);
var args = {
role: chk.attr("data-role"),
@@ -348,6 +349,7 @@ frappe.PermissionEngine = Class.extend({
method: "update",
args: args,
callback: function(r) {
frappe.dom.unfreeze();
if(r.exc) {
// exception: reverse
chk.prop("checked", !chk.prop("checked"));
@@ -374,8 +376,7 @@ frappe.PermissionEngine = Class.extend({
options:me.options.roles, reqd:1,fieldname:"role"},
{fieldtype:"Select", label:__("Permission Level"),
options:[0,1,2,3,4,5,6,7,8,9], reqd:1, fieldname: "permlevel",
description: __("Level 0 is for document level permissions, \
higher levels for field level permissions.")}
description: __("Level 0 is for document level permissions, higher levels for field level permissions.")}
]
});
if(me.get_doctype()) {


+ 273
- 271
frappe/custom/doctype/custom_field/custom_field.json Целия файл

@@ -58,382 +58,384 @@
],
"fields": [
{
"bold": 1,
"fieldname": "dt",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Document",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"search_index": 1
"bold": 1,
"fieldname": "dt",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Document",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"search_index": 1
},
{
"bold": 1,
"fieldname": "label",
"fieldtype": "Data",
"in_filter": 1,
"label": "Label",
"no_copy": 1,
"oldfieldname": "label",
"oldfieldtype": "Data"
"bold": 1,
"fieldname": "label",
"fieldtype": "Data",
"in_filter": 1,
"label": "Label",
"no_copy": 1,
"oldfieldname": "label",
"oldfieldtype": "Data"
},
{
"fieldname": "label_help",
"fieldtype": "HTML",
"label": "Label Help",
"oldfieldtype": "HTML"
"fieldname": "label_help",
"fieldtype": "HTML",
"label": "Label Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"no_copy": 1,
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Fieldname",
"no_copy": 1,
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1
},
{
"description": "Select the label after which you want to insert new field.",
"fieldname": "insert_after",
"fieldtype": "Select",
"label": "Insert After",
"no_copy": 1,
"oldfieldname": "insert_after",
"oldfieldtype": "Select"
"description": "Select the label after which you want to insert new field.",
"fieldname": "insert_after",
"fieldtype": "Select",
"label": "Insert After",
"no_copy": 1,
"oldfieldname": "insert_after",
"oldfieldtype": "Select"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"bold": 1,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"label": "Field Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"reqd": 1
"bold": 1,
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_filter": 1,
"in_list_view": 1,
"label": "Field Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"reqd": 1
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
},
{
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
},
{
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
},
{
"fieldname": "options_help",
"fieldtype": "HTML",
"label": "Options Help",
"oldfieldtype": "HTML"
"fieldname": "options_help",
"fieldtype": "HTML",
"label": "Options Help",
"oldfieldtype": "HTML"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
},
{
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On"
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On"
},
{
"fieldname": "default",
"fieldtype": "Text",
"label": "Default Value",
"oldfieldname": "default",
"oldfieldtype": "Text"
"fieldname": "default",
"fieldtype": "Text",
"label": "Default Value",
"oldfieldname": "default",
"oldfieldtype": "Text"
},
{
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"length": 255
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"length": 255
},
{
"fieldname": "description",
"fieldtype": "Text",
"label": "Field Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
"fieldname": "description",
"fieldtype": "Text",
"label": "Field Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
},
{
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"label": "Permission Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"label": "Permission Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
},
{
"fieldname": "width",
"fieldtype": "Data",
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data"
"fieldname": "width",
"fieldtype": "Data",
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data"
},
{
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
},
{
"fieldname": "properties",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
"width": "50%"
"fieldname": "properties",
"fieldtype": "Column Break",
"oldfieldtype": "Column Break",
"print_width": "50%",
"width": "50%"
},
{
"default": "0",
"fieldname": "reqd",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory Field",
"oldfieldname": "reqd",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "reqd",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Mandatory Field",
"oldfieldname": "reqd",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
"default": "0",
"depends_on": "eval:doc.fieldtype===\"Link\"",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
},
{
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden"
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden"
},
{
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
},
{
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 1,
"label": "Print Width",
"no_copy": 1,
"print_hide": 1
"fieldname": "print_width",
"fieldtype": "Data",
"hidden": 1,
"label": "Print Width",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"fieldname": "no_copy",
"fieldtype": "Check",
"label": "No Copy",
"oldfieldname": "no_copy",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "no_copy",
"fieldtype": "Check",
"label": "No Copy",
"oldfieldname": "no_copy",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
},
{
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
},
{
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
},
{
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
},
{
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 1,
"label": "Index",
"no_copy": 1,
"print_hide": 1
"default": "0",
"fieldname": "search_index",
"fieldtype": "Check",
"hidden": 1,
"label": "Index",
"no_copy": 1,
"print_hide": 1
},
{
"default": "0",
"description": "Don't HTML Encode HTML tags like &lt;script&gt; or just characters like &lt; or &gt;, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
"label": "Ignore XSS Filter"
"default": "0",
"description": "Don't HTML Encode HTML tags like &lt;script&gt; or just characters like &lt; or &gt;, as they could be intentionally used in this field",
"fieldname": "ignore_xss_filter",
"fieldtype": "Check",
"label": "Ignore XSS Filter"
},
{
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
},
{
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image', 'Int'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
},
{
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"length": 255
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"length": 255
},
{
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"length": 255
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"length": 255
},
{
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
},
{
"default": "0",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
"default": "0",
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_seconds",
"fieldtype": "Check",
"label": "Hide Seconds"
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_seconds",
"fieldtype": "Check",
"label": "Hide Seconds"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_days",
"fieldtype": "Check",
"label": "Hide Days"
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_days",
"fieldtype": "Check",
"label": "Hide Days"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
}
],
"icon": "fa fa-glass",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-02-06 23:43:00.123575",
"modified": "2020-08-28 11:28:44.377753",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Field",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"search_fields": "dt,label,fieldtype,options",


+ 78
- 174
frappe/custom/doctype/custom_script/custom_script.json Целия файл

@@ -1,187 +1,91 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2013-01-10 16:34:01",
"custom": 0,
"description": "Adds a client custom script to a DocType",
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB",
"actions": [],
"allow_import": 1,
"creation": "2013-01-10 16:34:01",
"description": "Adds a client custom script to a DocType",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"dt",
"enabled",
"script",
"sample"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "dt",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "DocType",
"length": 0,
"no_copy": 0,
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"permlevel": 0,
"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
},
"fieldname": "dt",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "DocType",
"oldfieldname": "dt",
"oldfieldtype": "Link",
"options": "DocType",
"reqd": 1,
"show_days": 1,
"show_seconds": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "script",
"fieldtype": "Code",
"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": "Script",
"length": 0,
"no_copy": 0,
"oldfieldname": "script",
"oldfieldtype": "Code",
"options": "JS",
"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
},
"fieldname": "script",
"fieldtype": "Code",
"label": "Script",
"oldfieldname": "script",
"oldfieldtype": "Code",
"options": "JS",
"show_days": 1,
"show_seconds": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sample",
"fieldtype": "HTML",
"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": "Sample",
"length": 0,
"no_copy": 0,
"options": "<h3>Custom Script Help</h3>\n<p>Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started</p>\n<pre><code>\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname); \ncur_frm.add_fetch('customer', 'local_tax_no', 'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task', 'validate', function(frm) {\n if (frm.doc.from_date &lt; get_today()) {\n msgprint('You can not select past date in From Date');\n validated = false;\n } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task', {\n refresh: function(frm) {\n // use the __islocal value of doc, to check if the doc is saved or not\n frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);\n } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task', {\n validate: function(frm) {\n if(user=='user1@example.com' &amp;&amp; frm.doc.purpose!='Material Receipt') {\n msgprint('You are only allowed Material Receipt');\n validated = false;\n }\n } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice', {\n validate: function(frm) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(frm.doc.sales_team, function(i, d) {\n // calculate incentive\n var incentive_percent = 2;\n if(frm.doc.base_grand_total &gt; 400) incentive_percent = 4;\n // actual incentive\n d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n frm.doc.total_incentive = total_incentive;\n } \n})\n\n</code></pre>",
"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
"fieldname": "sample",
"fieldtype": "HTML",
"label": "Sample",
"options": "<h3>Custom Script Help</h3>\n<p>Custom Scripts are executed only on the client-side (i.e. in Forms). Here are some examples to get you started</p>\n<pre><code>\n\n// fetch local_tax_no on selection of customer \n// cur_frm.add_fetch(link_field, source_fieldname, target_fieldname); \ncur_frm.add_fetch('customer', 'local_tax_no', 'local_tax_no');\n\n// additional validation on dates \nfrappe.ui.form.on('Task', 'validate', function(frm) {\n if (frm.doc.from_date &lt; get_today()) {\n msgprint('You can not select past date in From Date');\n validated = false;\n } \n});\n\n// make a field read-only after saving \nfrappe.ui.form.on('Task', {\n refresh: function(frm) {\n // use the __islocal value of doc, to check if the doc is saved or not\n frm.set_df_property('myfield', 'read_only', frm.doc.__islocal ? 0 : 1);\n } \n});\n\n// additional permission check\nfrappe.ui.form.on('Task', {\n validate: function(frm) {\n if(user=='user1@example.com' &amp;&amp; frm.doc.purpose!='Material Receipt') {\n msgprint('You are only allowed Material Receipt');\n validated = false;\n }\n } \n});\n\n// calculate sales incentive\nfrappe.ui.form.on('Sales Invoice', {\n validate: function(frm) {\n // calculate incentives for each person on the deal\n total_incentive = 0\n $.each(frm.doc.sales_team, function(i, d) {\n // calculate incentive\n var incentive_percent = 2;\n if(frm.doc.base_grand_total &gt; 400) incentive_percent = 4;\n // actual incentive\n d.incentives = flt(frm.doc.base_grand_total) * incentive_percent / 100;\n total_incentive += flt(d.incentives)\n });\n frm.doc.total_incentive = total_incentive;\n } \n})\n\n</code></pre>",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled",
"show_days": 1,
"show_seconds": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-glass",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2019-03-21 14:26:57.402994",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Script",
"owner": "Administrator",
],
"icon": "fa fa-glass",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-24 21:56:07.719579",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Script",
"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": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"sort_order": "ASC",
"track_changes": 1
}

+ 257
- 255
frappe/custom/doctype/customize_form_field/customize_form_field.json Целия файл

@@ -60,364 +60,366 @@
],
"fields": [
{
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"label": "Label and Type"
"fieldname": "label_and_type",
"fieldtype": "Section Break",
"label": "Label and Type"
},
{
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"oldfieldname": "label",
"oldfieldtype": "Data",
"search_index": 1
"fieldname": "label",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Label",
"oldfieldname": "label",
"oldfieldtype": "Data",
"search_index": 1
},
{
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
"reqd": 1,
"search_index": 1
"default": "Data",
"fieldname": "fieldtype",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Type",
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime",
"reqd": 1,
"search_index": 1
},
{
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Name",
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1,
"search_index": 1
"fieldname": "fieldname",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Name",
"oldfieldname": "fieldname",
"oldfieldtype": "Data",
"read_only": 1,
"search_index": 1
},
{
"default": "0",
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
"fieldname": "reqd",
"fieldtype": "Check",
"label": "Mandatory",
"oldfieldname": "reqd",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
"default": "0",
"depends_on": "eval:!in_list([\"Section Break\", \"Column Break\", \"Button\", \"HTML\"], doc.fieldtype)",
"fieldname": "reqd",
"fieldtype": "Check",
"label": "Mandatory",
"oldfieldname": "reqd",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
},
{
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
"default": "0",
"fieldname": "unique",
"fieldtype": "Check",
"label": "Unique"
},
{
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
"default": "0",
"fieldname": "in_list_view",
"fieldtype": "Check",
"label": "In List View"
},
{
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
"default": "0",
"fieldname": "in_standard_filter",
"fieldtype": "Check",
"label": "In Standard Filter"
},
{
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
"default": "0",
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
"fieldname": "in_global_search",
"fieldtype": "Check",
"label": "In Global Search"
},
{
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
"default": "0",
"fieldname": "bold",
"fieldtype": "Check",
"label": "Bold"
},
{
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
"default": "1",
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
"fieldname": "translatable",
"fieldtype": "Check",
"label": "Translatable"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
"description": "Set non-standard precision for a Float or Currency field",
"fieldname": "precision",
"fieldtype": "Select",
"label": "Precision",
"options": "\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9"
},
{
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
"fieldname": "length",
"fieldtype": "Int",
"label": "Length"
},
{
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
"fieldname": "options",
"fieldtype": "Small Text",
"in_list_view": 1,
"label": "Options",
"oldfieldname": "options",
"oldfieldtype": "Text"
},
{
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
"fieldname": "fetch_from",
"fieldtype": "Small Text",
"label": "Fetch From"
},
{
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
"default": "0",
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
"fieldname": "fetch_if_empty",
"fieldtype": "Check",
"label": "Fetch If Empty"
},
{
"fieldname": "permissions",
"fieldtype": "Section Break",
"label": "Permissions"
"fieldname": "permissions",
"fieldtype": "Section Break",
"label": "Permissions"
},
{
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age&gt;18",
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"oldfieldname": "depends_on",
"oldfieldtype": "Data",
"options": "JS"
"description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age&gt;18",
"fieldname": "depends_on",
"fieldtype": "Code",
"label": "Depends On",
"oldfieldname": "depends_on",
"oldfieldtype": "Data",
"options": "JS"
},
{
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Perm Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
"default": "0",
"fieldname": "permlevel",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Perm Level",
"oldfieldname": "permlevel",
"oldfieldtype": "Int"
},
{
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden",
"oldfieldname": "hidden",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
"default": "0",
"fieldname": "hidden",
"fieldtype": "Check",
"label": "Hidden",
"oldfieldname": "hidden",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
},
{
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
"default": "0",
"fieldname": "read_only",
"fieldtype": "Check",
"label": "Read Only"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
"default": "0",
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible",
"fieldtype": "Check",
"label": "Collapsible"
},
{
"default": "0",
"depends_on": "eval: doc.fieldtype == \"Table\"",
"fieldname": "allow_bulk_edit",
"fieldtype": "Check",
"label": "Allow Bulk Edit"
"default": "0",
"depends_on": "eval: doc.fieldtype == \"Table\"",
"fieldname": "allow_bulk_edit",
"fieldtype": "Check",
"label": "Allow Bulk Edit"
},
{
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On",
"options": "JS"
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
"fieldname": "collapsible_depends_on",
"fieldtype": "Code",
"label": "Collapsible Depends On",
"options": "JS"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
"default": "0",
"fieldname": "ignore_user_permissions",
"fieldtype": "Check",
"label": "Ignore User Permissions"
},
{
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "allow_on_submit",
"fieldtype": "Check",
"label": "Allow on Submit",
"oldfieldname": "allow_on_submit",
"oldfieldtype": "Check"
},
{
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "report_hide",
"fieldtype": "Check",
"label": "Report Hide",
"oldfieldname": "report_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:(doc.fieldtype == 'Link')",
"fieldname": "remember_last_selected_value",
"fieldtype": "Check",
"label": "Remember Last Selected Value"
"default": "0",
"depends_on": "eval:(doc.fieldtype == 'Link')",
"fieldname": "remember_last_selected_value",
"fieldtype": "Check",
"label": "Remember Last Selected Value"
},
{
"fieldname": "display",
"fieldtype": "Section Break",
"label": "Display"
"fieldname": "display",
"fieldtype": "Section Break",
"label": "Display"
},
{
"fieldname": "default",
"fieldtype": "Text",
"label": "Default",
"oldfieldname": "default",
"oldfieldtype": "Text"
"fieldname": "default",
"fieldtype": "Text",
"label": "Default",
"oldfieldname": "default",
"oldfieldtype": "Text"
},
{
"default": "0",
"fieldname": "in_filter",
"fieldtype": "Check",
"label": "In Filter",
"oldfieldname": "in_filter",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
"default": "0",
"fieldname": "in_filter",
"fieldtype": "Check",
"label": "In Filter",
"oldfieldname": "in_filter",
"oldfieldtype": "Check",
"print_width": "50px",
"width": "50px"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"fieldname": "description",
"fieldtype": "Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
"fieldname": "description",
"fieldtype": "Text",
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Text",
"print_width": "300px",
"width": "300px"
},
{
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
"default": "0",
"fieldname": "print_hide",
"fieldtype": "Check",
"label": "Print Hide",
"oldfieldname": "print_hide",
"oldfieldtype": "Check"
},
{
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
"default": "0",
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
"fieldname": "print_hide_if_no_value",
"fieldtype": "Check",
"label": "Print Hide If No Value"
},
{
"description": "Print Width of the field, if the field is a column in a table",
"fieldname": "print_width",
"fieldtype": "Data",
"label": "Print Width",
"print_width": "50px",
"width": "50px"
"description": "Print Width of the field, if the field is a column in a table",
"fieldname": "print_width",
"fieldtype": "Data",
"label": "Print Width",
"print_width": "50px",
"width": "50px"
},
{
"depends_on": "eval:cur_frm.doc.istable",
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
"depends_on": "eval:cur_frm.doc.istable",
"description": "Number of columns for a field in a Grid (Total Columns in a grid should be less than 11)",
"fieldname": "columns",
"fieldtype": "Int",
"label": "Columns"
},
{
"fieldname": "width",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data",
"print_width": "50px",
"width": "50px"
"fieldname": "width",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Width",
"oldfieldname": "width",
"oldfieldtype": "Data",
"print_width": "50px",
"width": "50px"
},
{
"default": "0",
"fieldname": "is_custom_field",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Custom Field",
"read_only": 1
"default": "0",
"fieldname": "is_custom_field",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Custom Field",
"read_only": 1
},
{
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
"default": "0",
"fieldname": "allow_in_quick_entry",
"fieldtype": "Check",
"label": "Allow in Quick Entry"
},
{
"fieldname": "property_depends_on_section",
"fieldtype": "Section Break",
"label": "Property Depends On"
"fieldname": "property_depends_on_section",
"fieldtype": "Section Break",
"label": "Property Depends On"
},
{
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"options": "JS"
"fieldname": "mandatory_depends_on",
"fieldtype": "Code",
"label": "Mandatory Depends On",
"options": "JS"
},
{
"fieldname": "column_break_33",
"fieldtype": "Column Break"
"fieldname": "column_break_33",
"fieldtype": "Column Break"
},
{
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"options": "JS"
"fieldname": "read_only_depends_on",
"fieldtype": "Code",
"label": "Read Only Depends On",
"options": "JS"
},
{
"default": "0",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
"default": "0",
"depends_on": "eval:!in_list(['Table', 'Table MultiSelect'], doc.fieldtype);",
"fieldname": "in_preview",
"fieldtype": "Check",
"label": "In Preview"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_seconds",
"fieldtype": "Check",
"label": "Hide Seconds"
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_seconds",
"fieldtype": "Check",
"label": "Hide Seconds"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_days",
"fieldtype": "Check",
"label": "Hide Days"
"default": "0",
"depends_on": "eval:doc.fieldtype=='Duration'",
"fieldname": "hide_days",
"fieldtype": "Check",
"label": "Hide Days"
},
{
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
"default": "0",
"depends_on": "eval:doc.fieldtype=='Section Break'",
"fieldname": "hide_border",
"fieldtype": "Check",
"label": "Hide Border"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-06-02 23:45:46.810868",
"modified": "2020-08-28 11:28:59.084060",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customize Form Field",


+ 2
- 0
frappe/database/mariadb/setup_db.py Целия файл

@@ -92,6 +92,8 @@ def bootstrap_database(db_name, verbose, source_sql=None):
sys.exit(1)

import_db_from_sql(source_sql, verbose)

frappe.connect(db_name=db_name)
if not 'tabDefaultValue' in frappe.db.get_tables():
print('''Database not installed, this can due to lack of permission, or that the database name exists.
Check your mysql root password, or use --force to reinstall''')


+ 14
- 1
frappe/desk/desktop.py Целия файл

@@ -203,7 +203,7 @@ class Workspace:
cards = cards + get_custom_reports_and_doctypes(self.doc.module)

if len(self.extended_cards):
cards = cards + self.extended_cards
cards = merge_cards_based_on_label(cards + self.extended_cards)
default_country = frappe.db.get_default("country")

def _doctype_contains_a_record(name):
@@ -579,3 +579,16 @@ def update_onboarding_step(name, field, value):

"""
frappe.db.set_value("Onboarding Step", name, field, value)

def merge_cards_based_on_label(cards):
"""Merge cards with common label."""
cards_dict = {}
for card in cards:
if card.label in cards_dict:
links = loads(cards_dict[card.label].links) + loads(card.links)
cards_dict[card.label].update(dict(links=dumps(links)))
cards_dict[card.label] = cards_dict.pop(card.label)
else:
cards_dict[card.label] = card

return list(cards_dict.values())

+ 1
- 1
frappe/desk/form/meta.py Целия файл

@@ -130,7 +130,7 @@ class FormMeta(Meta):
def add_custom_script(self):
"""embed all require files"""
# custom script
custom = frappe.db.get_value("Custom Script", {"dt": self.name}, "script") or ""
custom = frappe.db.get_value("Custom Script", {"dt": self.name, "enabled": 1}, "script") or ""

self.set("__custom_js", custom)



+ 5
- 3
frappe/desk/link_preview.py Целия файл

@@ -1,5 +1,5 @@
import frappe
from frappe.model import no_value_fields
from frappe.model import no_value_fields, table_fields
import json

@frappe.whitelist()
@@ -9,11 +9,13 @@ def get_preview_data(doctype, docname):
if not meta.show_preview_popup: return

preview_fields = [field.fieldname for field in meta.fields \
if field.in_preview and field.fieldtype not in no_value_fields]
if field.in_preview and field.fieldtype not in no_value_fields \
and field.fieldtype not in table_fields]

# no preview fields defined, build list from mandatory fields
if not preview_fields:
preview_fields = [field.fieldname for field in meta.fields if field.reqd]
preview_fields = [field.fieldname for field in meta.fields if field.reqd \
and field.fieldtype not in table_fields]

title_field = meta.get_title_field()
image_field = meta.image_field


+ 32
- 4
frappe/desk/query_report.py Целия файл

@@ -216,20 +216,33 @@ def add_data_to_custom_columns(columns, result):
return data

def reorder_data_for_custom_columns(custom_columns, columns, result, report_type):
custom_column_labels = [col["label"] for col in custom_columns]
if not result:
return []

if report_type == 'Query Report':
# Assume list result for query reports
# Query report columns exclusively use Label
custom_column_labels = [col["label"] for col in custom_columns]
original_column_labels = [col.split(":")[0] for col in columns]
return get_columns_from_list(custom_column_labels, original_column_labels, result)

custom_column_names = [col["fieldname"] for col in custom_columns]
if isinstance(result[0], list) or isinstance(result[0], tuple):
# If the result is a list of lists
original_column_names = [col["fieldname"] for col in columns]
return get_columns_from_list(custom_column_names, original_column_names, result)
else:
original_column_labels = [col["label"] for col in columns]
# If the result is a list of dicts
return get_columns_from_dict(custom_column_names, result)

def get_columns_from_list(columns, target_columns, result):
reordered_result = []

for res in result:
r = []
for col_name in custom_column_labels:
for col_name in columns:
try:
idx = original_column_labels.index(col_name)
idx = target_columns.index(col_name)
r.append(res[idx])
except ValueError:
pass
@@ -238,6 +251,21 @@ def reorder_data_for_custom_columns(custom_columns, columns, result, report_type

return reordered_result

def get_columns_from_dict(columns, result):
reordered_result = []

for res in result:
r = {}
for col_name in columns:
try:
r[col_name] = res[col_name]
except KeyError:
pass

reordered_result.append(r)

return reordered_result

def get_prepared_report_result(report, filters, dn="", user=None):
latest_report_data = {}
doc = None


+ 0
- 3
frappe/email/doctype/newsletter/newsletter.js Целия файл

@@ -14,9 +14,6 @@ frappe.ui.form.on('Newsletter', {
});
}, "fa fa-play", "btn-success");
}
if (!doc.__islocal && cint(doc.email_sent)) {
frm.set_df_property('schedule_send', "read_only", 1);
}

frm.events.setup_dashboard(frm);



+ 39
- 9
frappe/email/doctype/newsletter/newsletter.json Целия файл

@@ -15,7 +15,10 @@
"email_sent",
"newsletter_content",
"subject",
"content_type",
"message",
"message_md",
"message_html",
"send_unsubscribe_link",
"send_attachments",
"published",
@@ -37,8 +40,7 @@
"fieldname": "send_from",
"fieldtype": "Data",
"ignore_xss_filter": 1,
"label": "Sender",
"no_copy": 1
"label": "Sender"
},
{
"default": "0",
@@ -50,7 +52,8 @@
},
{
"fieldname": "newsletter_content",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Content"
},
{
"fieldname": "subject",
@@ -61,11 +64,12 @@
"reqd": 1
},
{
"depends_on": "eval: doc.content_type === 'Rich Text'",
"fieldname": "message",
"fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Message",
"reqd": 1
"mandatory_depends_on": "eval: doc.content_type === 'Rich Text'"
},
{
"default": "1",
@@ -87,16 +91,20 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "test_the_newsletter",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Testing"
},
{
"description": "A Lead with this Email Address should exist",
"fieldname": "test_email_id",
"fieldtype": "Data",
"label": "Test Email Address"
"label": "Test Email Address",
"options": "Email"
},
{
"depends_on": "eval: doc.test_email_id",
"fieldname": "test_send",
"fieldtype": "Button",
"label": "Test",
@@ -117,7 +125,8 @@
"depends_on": "eval: doc.schedule_sending",
"fieldname": "schedule_send",
"fieldtype": "Datetime",
"label": "Schedule Send"
"label": "Schedule Send",
"read_only_depends_on": "eval: doc.email_sent"
},
{
"default": "0",
@@ -125,11 +134,32 @@
"fieldtype": "Check",
"label": "Send Attachments"
},
{
"fieldname": "content_type",
"fieldtype": "Select",
"label": "Content Type",
"options": "Rich Text\nMarkdown\nHTML"
},
{
"depends_on": "eval:doc.content_type === 'Markdown'",
"fieldname": "message_md",
"fieldtype": "Markdown Editor",
"label": "Message (Markdown)",
"mandatory_depends_on": "eval:doc.content_type === 'Markdown'"
},
{
"depends_on": "eval:doc.content_type === 'HTML'",
"fieldname": "message_html",
"fieldtype": "HTML Editor",
"label": "Message (HTML)",
"mandatory_depends_on": "eval:doc.content_type === 'HTML'"
},
{
"default": "0",
"fieldname": "schedule_sending",
"fieldtype": "Check",
"label": "Schedule Sending"
"label": "Schedule Sending",
"read_only_depends_on": "eval: doc.email_sent"
}
],
"has_web_view": 1,
@@ -139,7 +169,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 3,
"modified": "2020-08-17 18:11:59.541686",
"modified": "2020-08-24 19:59:37.262500",
"modified_by": "Administrator",
"module": "Email",
"name": "Newsletter",


+ 19
- 21
frappe/email/doctype/newsletter/newsletter.py Целия файл

@@ -8,12 +8,9 @@ import frappe.utils
from frappe import throw, _
from frappe.website.website_generator import WebsiteGenerator
from frappe.utils.verified_command import get_signed_params, verify_request
from frappe.utils.background_jobs import enqueue
from frappe.email.queue import send
from frappe.email.doctype.email_group.email_group import add_subscribers
from frappe.utils import parse_addr, now_datetime
from frappe.utils import validate_email_address

from frappe.utils import parse_addr, now_datetime, markdown, validate_email_address

class Newsletter(WebsiteGenerator):
def onload(self):
@@ -29,8 +26,8 @@ class Newsletter(WebsiteGenerator):

def test_send(self, doctype="Lead"):
self.recipients = frappe.utils.split_emails(self.test_email_id)
self.queue_all()
frappe.msgprint(_("Scheduled to send to {0}").format(self.test_email_id))
self.queue_all(test_email=True)
frappe.msgprint(_("Test email sent to {0}").format(self.test_email_id))

def send_emails(self):
"""send emails to leads and customers"""
@@ -40,21 +37,13 @@ class Newsletter(WebsiteGenerator):
self.recipients = self.get_recipients()

if self.recipients:
if getattr(frappe.local, "is_ajax", False):
self.validate_send()
# using default queue with a longer timeout as this isn't a scheduled task
enqueue(send_newsletter, queue='default', timeout=6000, event='send_newsletter',
newsletter=self.name)

else:
self.queue_all()

frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients)))
self.queue_all()
frappe.msgprint(_("Email queued to {0} recipients").format(len(self.recipients)))

else:
frappe.msgprint(_("Newsletter should have atleast one recipient"))

def queue_all(self):
def queue_all(self, test_email=False):
if not self.get("recipients"):
# in case it is called via worker
self.recipients = self.get_recipients()
@@ -80,7 +69,7 @@ class Newsletter(WebsiteGenerator):
frappe.throw(_("Unable to find attachment {0}").format(file.name))

send(recipients=self.recipients, sender=sender,
subject=self.subject, message=self.message,
subject=self.subject, message=self.get_message(),
reference_doctype=self.doctype, reference_name=self.name,
add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments,
unsubscribe_method="/unsubscribe",
@@ -90,9 +79,18 @@ class Newsletter(WebsiteGenerator):
if not frappe.flags.in_test:
frappe.db.auto_commit_on_many_writes = False

self.db_set("email_sent", 1)
self.db_set("schedule_send", now_datetime())
self.db_set("scheduled_to_send", len(self.recipients))
if not test_email:
self.db_set("email_sent", 1)
self.db_set("schedule_send", now_datetime())
self.db_set("scheduled_to_send", len(self.recipients))

def get_message(self):
return {
'Rich Text': self.message,
'Markdown': markdown(self.message_md),
'HTML': self.message_html
}[self.content_type]

def get_recipients(self):
"""Get recipients from Email Group"""


+ 4
- 2
frappe/email/doctype/newsletter/newsletter_list.js Целия файл

@@ -1,8 +1,10 @@
frappe.listview_settings['Newsletter'] = {
add_fields: ["subject", "email_sent"],
add_fields: ["subject", "email_sent", "schedule_sending"],
get_indicator: function(doc) {
if(doc.email_sent) {
if (doc.email_sent) {
return [__("Sent"), "green", "email_sent,=,Yes"];
} else if (doc.schedule_sending) {
return [__("Scheduled"), "orange", "email_sent,=,No|schedule_sending,=,Yes"];
} else {
return [__("Not Sent"), "orange", "email_sent,=,No"];
}


+ 1
- 0
frappe/email/doctype/newsletter/test_newsletter.py Целия файл

@@ -67,6 +67,7 @@ class TestNewsletter(unittest.TestCase):
"doctype": "Newsletter",
"subject": "_Test Newsletter",
"send_from": "Test Sender <test_sender@example.com>",
"content_type": "Rich Text",
"message": "Testing my news.",
"published": published,
"schedule_sending": bool(schedule_send),


+ 2
- 1
frappe/hooks.py Целия файл

@@ -136,7 +136,8 @@ doc_events = {
"frappe.workflow.doctype.workflow_action.workflow_action.process_workflow_actions",
"frappe.automation.doctype.assignment_rule.assignment_rule.apply",
"frappe.automation.doctype.milestone_tracker.milestone_tracker.evaluate_milestone",
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers"
"frappe.core.doctype.file.file.attach_files_to_document",
"frappe.event_streaming.doctype.event_update_log.event_update_log.notify_consumers",
],
"after_rename": "frappe.desk.notifications.clear_doctype_notifications",
"on_cancel": [


+ 4
- 1
frappe/installer.py Целия файл

@@ -359,8 +359,11 @@ def is_downgrade(sql_file_path, verbose=False):
if head in line:
# 'line' (str) format: ('2056588823','2020-05-11 18:21:31.488367','2020-06-12 11:49:31.079506','Administrator','Administrator',0,'Installed Applications','installed_applications','Installed Applications',1,'frappe','v10.1.71-74 (3c50d5e) (v10.x.x)','v10.x.x'),('855c640b8e','2020-05-11 18:21:31.488367','2020-06-12 11:49:31.079506','Administrator','Administrator',0,'Installed Applications','installed_applications','Installed Applications',2,'your_custom_app','0.0.1','master')
line = line.strip().lstrip(head).rstrip(";").strip()
app_rows = frappe.safe_eval(line)
# check if iterable consists of tuples before trying to transform
apps_list = app_rows if all(isinstance(app_row, (tuple, list, set)) for app_row in app_rows) else (app_rows, )
# 'all_apps' (list) format: [('frappe', '12.x.x-develop ()', 'develop'), ('your_custom_app', '0.0.1', 'master')]
all_apps = [ x[-3:] for x in frappe.safe_eval(line) ]
all_apps = [ x[-3:] for x in apps_list ]

for app in all_apps:
app_name = app[0]


+ 8
- 8
frappe/integrations/desk_page/integrations/integrations.json Целия файл

@@ -10,11 +10,6 @@
"label": "Google Services",
"links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Webhook",
"links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Authentication",
@@ -23,7 +18,12 @@
{
"hidden": 0,
"label": "Payments",
"links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Paytm payment gateway settings\",\n \"label\": \"Paytm Settings\",\n \"name\": \"Paytm Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Settings",
"links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Twilio Settings for WhatsApp integration\",\n \"label\": \"Twilio Settings\",\n \"name\": \"Twilio Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"SMS Settings for sending sms\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Administration",
@@ -34,11 +34,11 @@
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"icon": "frapicon-dashboard",
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Integrations",
"modified": "2020-04-01 11:24:40.751651",
"modified": "2020-08-20 23:04:04.528572",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integrations",


+ 16
- 5
frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py Целия файл

@@ -8,7 +8,7 @@ import os.path
import frappe
import boto3
from frappe import _
from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size
from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size, generate_files_backup
from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils.background_jobs import enqueue
@@ -125,6 +125,11 @@ def backup_to_s3():
else:
if backup_files:
db_filename, site_config, files_filename, private_files = get_latest_backup_file(with_files=backup_files)

if not files_filename or not private_files:
generate_files_backup()
db_filename, site_config, files_filename, private_files = get_latest_backup_file(with_files=backup_files)

else:
db_filename, site_config = get_latest_backup_file()

@@ -133,9 +138,14 @@ def backup_to_s3():

upload_file_to_s3(db_filename, folder, conn, bucket)
upload_file_to_s3(site_config, folder, conn, bucket)

if backup_files:
upload_file_to_s3(private_files, folder, conn, bucket)
upload_file_to_s3(files_filename, folder, conn, bucket)
if private_files:
upload_file_to_s3(private_files, folder, conn, bucket)

if files_filename:
upload_file_to_s3(files_filename, folder, conn, bucket)

delete_old_backups(doc.backup_limit, bucket)


@@ -160,14 +170,15 @@ def delete_old_backups(limit, bucket):
aws_access_key_id=doc.access_key_id,
aws_secret_access_key=doc.get_password('secret_access_key'),
endpoint_url=doc.endpoint_url or 'https://s3.amazonaws.com'
)
)

bucket = s3.Bucket(bucket)
objects = bucket.meta.client.list_objects_v2(Bucket=bucket.name, Delimiter='/')
if objects:
for obj in objects.get('CommonPrefixes'):
all_backups.append(obj.get('Prefix'))

oldest_backup = sorted(all_backups)[0]
oldest_backup = sorted(all_backups)[0] if all_backups else ''

if len(all_backups) > backup_limit:
print("Deleting Backup: {0}".format(oldest_backup))


+ 5
- 1
frappe/integrations/doctype/twilio_number_group/twilio_number_group.json Целия файл

@@ -14,12 +14,16 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "Phone Number",
"options": "Phone",
"show_days": 1,
"show_seconds": 1,
"unique": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-03-02 14:54:34.396254",
"modified": "2020-08-20 22:48:57.166791",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Twilio Number Group",


+ 10
- 1
frappe/integrations/doctype/twilio_settings/twilio_settings.py Целия файл

@@ -11,7 +11,16 @@ from frappe.utils.password import get_decrypted_password
from six import string_types

class TwilioSettings(Document):
pass
def validate(self):
self.validate_twilio_credentials()

def validate_twilio_credentials(self):
try:
auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", 'auth_token')
client = Client(self.account_sid, auth_token)
client.api.accounts(self.account_sid).fetch()
except Exception:
frappe.throw(_("Invalid Account SID or Auth Token."))

def send_whatsapp_message(sender, receiver_list, message):
import json


+ 10
- 0
frappe/integrations/offsite_backup_utils.py Целия файл

@@ -90,3 +90,13 @@ def validate_file_size():

if file_size > 1:
frappe.flags.create_new_backup = False

def generate_files_backup():
from frappe.utils.backups import BackupGenerator

backup = BackupGenerator(frappe.conf.db_name, frappe.conf.db_name,
frappe.conf.db_password, db_host = frappe.db.host,
db_type=frappe.conf.db_type, db_port=frappe.conf.db_port)

backup.set_backup_file_name()
backup.zip_files()

+ 6
- 1
frappe/model/base_document.py Целия файл

@@ -299,7 +299,12 @@ class BaseDocument(object):
return frappe.as_json(self.as_dict())

def get_table_field_doctype(self, fieldname):
return self.meta.get_field(fieldname).options
try:
return self.meta.get_field(fieldname).options
except AttributeError:
if self.doctype == 'DocType':
return dict(links='DocType Link', actions='DocType Action').get(fieldname)
raise

def get_parentfield_of_doctype(self, doctype):
fieldname = [df.fieldname for df in self.meta.get_table_fields() if df.options==doctype]


+ 6
- 0
frappe/model/utils/rename_field.py Целия файл

@@ -19,11 +19,17 @@ def rename_field(doctype, old_fieldname, new_fieldname):
print("rename_field: " + (new_fieldname) + " not found in " + doctype)
return

if not meta.issingle and not frappe.db.has_column(doctype, old_fieldname):
print("rename_field: " + (old_fieldname) + " not found in table for: " + doctype)
# never had the field?
return

if new_field.fieldtype in table_fields:
# change parentfield of table mentioned in options
frappe.db.sql("""update `tab%s` set parentfield=%s
where parentfield=%s""" % (new_field.options.split("\n")[0], "%s", "%s"),
(new_fieldname, old_fieldname))

elif new_field.fieldtype not in no_value_fields:
if meta.issingle:
frappe.db.sql("""update `tabSingles` set field=%s


+ 8
- 2
frappe/patches.txt Целия файл

@@ -15,7 +15,7 @@ execute:frappe.reload_doc('core', 'doctype', 'custom_docperm')
execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29
execute:frappe.reload_doc('core', 'doctype', 'comment')
frappe.patches.v8_0.drop_is_custom_from_docperm
execute:frappe.reload_doc('core', 'doctype', 'module_def') #2017-09-22
execute:frappe.reload_doc('core', 'doctype', 'module_def') #2020-08-28
execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01
execute:frappe.reload_doc('email', 'doctype', 'document_follow')
execute:frappe.reload_doc('core', 'doctype', 'communication_link') #2019-10-02
@@ -40,7 +40,9 @@ execute:frappe.reload_doc('core', 'doctype', 'role') #2017-05-23
execute:frappe.reload_doc('core', 'doctype', 'user') #2017-10-27
execute:frappe.reload_doc('custom', 'doctype', 'custom_field') #2015-10-19
execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26
execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03
execute:frappe.reload_doc('core', 'doctype', 'report_column')
execute:frappe.reload_doc('core', 'doctype', 'report_filter')
execute:frappe.reload_doc('core', 'doctype', 'report') #2020-08-25
execute:frappe.reload_doc('core', 'doctype', 'translation') #2016-03-03
execute:frappe.reload_doc('email', 'doctype', 'email_alert') #2014-07-15
execute:frappe.reload_doc('desk', 'doctype', 'todo') #2014-12-31-1
@@ -299,5 +301,9 @@ frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart
frappe.patches.v13_0.add_standard_navbar_items
frappe.patches.v13_0.generate_theme_files_in_public_folder
frappe.patches.v13_0.increase_password_length
frappe.patches.v12_0.fix_email_id_formatting
frappe.patches.v13_0.add_toggle_width_in_navbar_settings
frappe.patches.v13_0.rename_notification_fields
frappe.patches.v13_0.remove_duplicate_navbar_items
frappe.patches.v13_0.enable_custom_script
frappe.patches.v13_0.update_newsletter_content_type

+ 44
- 0
frappe/patches/v12_0/fix_email_id_formatting.py Целия файл

@@ -0,0 +1,44 @@
import frappe

def execute():
fix_communications()
fix_show_as_cc_email_queue()
fix_email_queue_recipients()

def fix_communications():
for communication in frappe.db.sql('''select name, recipients, cc, bcc from tabCommunication
where creation > '2020-06-01'
and communication_medium='Email'
and communication_type='Communication'
and (cc like '%&lt;%' or bcc like '%&lt;%' or recipients like '%&lt;%')
''', as_dict=1):

communication['recipients'] = format_email_id(communication.recipients)
communication['cc'] = format_email_id(communication.cc)
communication['bcc'] = format_email_id(communication.bcc)

frappe.db.sql('''update `tabCommunication` set recipients=%s,cc=%s,bcc=%s
where name =%s ''', (communication['recipients'], communication['cc'],
communication['bcc'], communication['name']))

def fix_show_as_cc_email_queue():
for queue in frappe.get_all("Email Queue", {'creation': ['>', '2020-06-01'],
'status': 'Not Sent', 'show_as_cc': ['like', '%&lt;%']},
['name', 'show_as_cc']):

frappe.db.set_value('Email Queue', queue['name'],
'show_as_cc', format_email_id(queue['show_as_cc']))

def fix_email_queue_recipients():
for recipient in frappe.db.sql('''select recipient, name from
`tabEmail Queue Recipient` where recipient like '%&lt;%'
and status='Not Sent' and creation > '2020-06-01' ''', as_dict=1):

frappe.db.set_value('Email Queue Recipient', recipient['name'],
'recipient', format_email_id(recipient['recipient']))

def format_email_id(email):
if email and ('&lt;' in email and '&gt;' in email):
return email.replace('&gt;', '>').replace('&lt;', '<')

return email

+ 2
- 0
frappe/patches/v12_0/remove_deprecated_fields_from_doctype.py Целия файл

@@ -1,6 +1,8 @@
import frappe

def execute():
frappe.reload_doc('core', 'doctype', 'doctype_link')
frappe.reload_doc('core', 'doctype', 'doctype_action')
frappe.reload_doc('core', 'doctype', 'doctype')
frappe.model.delete_fields({
'DocType': ['hide_heading', 'image_view', 'read_only_onload']


+ 3
- 0
frappe/patches/v13_0/add_toggle_width_in_navbar_settings.py Целия файл

@@ -4,6 +4,9 @@ import frappe
def execute():
navbar_settings = frappe.get_single("Navbar Settings")

if frappe.db.exists('Navbar Item', {'item_label': 'Toggle Full Width'}):
return

for navbar_item in navbar_settings.settings_dropdown[5:]:
navbar_item.idx = navbar_item.idx + 1



+ 13
- 0
frappe/patches/v13_0/enable_custom_script.py Целия файл

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

from __future__ import unicode_literals
import frappe

def execute():
"""Enable all the existing custom script"""
frappe.reload_doc("Custom", "doctype", "Custom Script")

frappe.db.sql("""
UPDATE `tabCustom Script` SET enabled=1
""")

+ 14
- 0
frappe/patches/v13_0/remove_duplicate_navbar_items.py Целия файл

@@ -0,0 +1,14 @@
from __future__ import unicode_literals
import frappe

def execute():
navbar_settings = frappe.get_single("Navbar Settings")
duplicate_items = []

for navbar_item in navbar_settings.settings_dropdown:
if navbar_item.item_label == 'Toggle Full Width':
duplicate_items.append(navbar_item)

if len(duplicate_items) > 1:
navbar_settings.remove(duplicate_items[0])
navbar_settings.save()

+ 12
- 0
frappe/patches/v13_0/update_newsletter_content_type.py Целия файл

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

from __future__ import unicode_literals
import frappe

def execute():
frappe.reload_doc('email', 'doctype', 'Newsletter')
frappe.db.sql("""
UPDATE tabNewsletter
SET content_type = 'Rich Text'
""")

+ 6
- 3
frappe/printing/doctype/print_format/print_format.json Целия файл

@@ -1,4 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "Prompt",
"creation": "2013-01-23 19:54:43",
@@ -181,7 +182,7 @@
"fieldname": "print_format_help",
"fieldtype": "HTML",
"label": "Print Format Help",
"options": "<h3>Print Format Help</h3>\n<hr>\n<h4>Introduction</h4>\n<p>Print itemsFormats are rendered on the server side using the Jinja Templating Language. All forms have access to the <code>doc</code> object which contains information about the document that is being formatted. You can also access common utilities via the <code>frappe</code> module.</p>\n<p>For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.</p>\n<hr>\n<h4>References</h4>\n<ol>\n\t<li><a href=\"http://jinja.pocoo.org/docs/templates/\" target=\"_blank\">Jinja Tempalting Language: Reference</a></li>\n\t<li><a href=\"http://getbootstrap.com\" target=\"_blank\">Bootstrap CSS Framework</a></li>\n</ol>\n<hr>\n<h4>Example</h4>\n<pre><code>&lt;h3&gt;{{ doc.select_print_heading or \"Invoice\" }}&lt;/h3&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-md-3 text-right\"&gt;Customer Name&lt;/div&gt;\n\t&lt;div class=\"col-md-9\"&gt;{{ doc.customer_name }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-md-3 text-right\"&gt;Date&lt;/div&gt;\n\t&lt;div class=\"col-md-9\"&gt;{{ doc.get_formatted(\"invoice_date\") }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;table class=\"table table-bordered\"&gt;\n\t&lt;tbody&gt;\n\t\t&lt;tr&gt;\n\t\t\t&lt;th&gt;Sr&lt;/th&gt;\n\t\t\t&lt;th&gt;Item Name&lt;/th&gt;\n\t\t\t&lt;th&gt;Description&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Qty&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Rate&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Amount&lt;/th&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- for row in doc.items -%}\n\t\t&lt;tr&gt;\n\t\t\t&lt;td style=\"width: 3%;\"&gt;{{ row.idx }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 20%;\"&gt;\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t&lt;br&gt;Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 37%;\"&gt;\n\t\t\t\t&lt;div style=\"border: 0px;\"&gt;{{ row.description }}&lt;/div&gt;&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 10%; text-align: right;\"&gt;{{ row.qty }} {{ row.uom or row.stock_uom }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}&lt;/td&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- endfor -%}\n\t&lt;/tbody&gt;\n&lt;/table&gt;</code></pre>\n<hr>\n<h4>Common Functions</h4>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%;\"><code>doc.get_formatted(\"[fieldname]\", [parent_doc])</code></td>\n\t\t\t<td>Get document value formatted as Date, Currency etc. Pass parent <code>doc</code> for curreny type fields.</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%;\"><code>frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")</code></td>\n\t\t\t<td>Get value from another document.</td>\n\t\t</tr>\n\t</tbody>\n</table>\n"
"options": "<h3>Print Format Help</h3>\n<hr>\n<h4>Introduction</h4>\n<p>Print Formats are rendered on the server side using the Jinja Templating Language. All forms have access to the <code>doc</code> object which contains information about the document that is being formatted. You can also access common utilities via the <code>frappe</code> module.</p>\n<p>For styling, the Boostrap CSS framework is provided and you can enjoy the full range of classes.</p>\n<hr>\n<h4>References</h4>\n<ol>\n\t<li><a href=\"http://jinja.pocoo.org/docs/templates/\" target=\"_blank\">Jinja Templating Language</a></li>\n\t<li><a href=\"http://getbootstrap.com\" target=\"_blank\">Bootstrap CSS Framework</a></li>\n</ol>\n<hr>\n<h4>Example</h4>\n<pre><code>&lt;h3&gt;{{ doc.select_print_heading or \"Invoice\" }}&lt;/h3&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-md-3 text-right\"&gt;Customer Name&lt;/div&gt;\n\t&lt;div class=\"col-md-9\"&gt;{{ doc.customer_name }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;div class=\"row\"&gt;\n\t&lt;div class=\"col-md-3 text-right\"&gt;Date&lt;/div&gt;\n\t&lt;div class=\"col-md-9\"&gt;{{ doc.get_formatted(\"invoice_date\") }}&lt;/div&gt;\n&lt;/div&gt;\n&lt;table class=\"table table-bordered\"&gt;\n\t&lt;tbody&gt;\n\t\t&lt;tr&gt;\n\t\t\t&lt;th&gt;Sr&lt;/th&gt;\n\t\t\t&lt;th&gt;Item Name&lt;/th&gt;\n\t\t\t&lt;th&gt;Description&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Qty&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Rate&lt;/th&gt;\n\t\t\t&lt;th class=\"text-right\"&gt;Amount&lt;/th&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- for row in doc.items -%}\n\t\t&lt;tr&gt;\n\t\t\t&lt;td style=\"width: 3%;\"&gt;{{ row.idx }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 20%;\"&gt;\n\t\t\t\t{{ row.item_name }}\n\t\t\t\t{% if row.item_code != row.item_name -%}\n\t\t\t\t&lt;br&gt;Item Code: {{ row.item_code}}\n\t\t\t\t{%- endif %}\n\t\t\t&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 37%;\"&gt;\n\t\t\t\t&lt;div style=\"border: 0px;\"&gt;{{ row.description }}&lt;/div&gt;&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 10%; text-align: right;\"&gt;{{ row.qty }} {{ row.uom or row.stock_uom }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"rate\", doc) }}&lt;/td&gt;\n\t\t\t&lt;td style=\"width: 15%; text-align: right;\"&gt;{{\n\t\t\t\trow.get_formatted(\"amount\", doc) }}&lt;/td&gt;\n\t\t&lt;/tr&gt;\n\t\t{%- endfor -%}\n\t&lt;/tbody&gt;\n&lt;/table&gt;</code></pre>\n<hr>\n<h4>Common Functions</h4>\n<table class=\"table table-bordered\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%;\"><code>doc.get_formatted(\"[fieldname]\", [parent_doc])</code></td>\n\t\t\t<td>Get document value formatted as Date, Currency, etc. Pass parent <code>doc</code> for currency type fields.</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td style=\"width: 30%;\"><code>frappe.db.get_value(\"[doctype]\", \"[name]\", \"fieldname\")</code></td>\n\t\t\t<td>Get value from another document.</td>\n\t\t</tr>\n\t</tbody>\n</table>\n"
},
{
"fieldname": "format_data",
@@ -199,8 +200,10 @@
],
"icon": "fa fa-print",
"idx": 1,
"modified": "2019-11-28 12:40:40.364699",
"modified_by": "faris@erpnext.com",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-29 11:44:59.082797",
"modified_by": "Administrator",
"module": "Printing",
"name": "Print Format",
"owner": "Administrator",


+ 7
- 0
frappe/public/js/frappe/file_uploader/FileUploader.vue Целия файл

@@ -144,6 +144,9 @@ export default {
docname: {
default: null
},
fieldname: {
default: null
},
folder: {
default: 'Home'
},
@@ -406,6 +409,10 @@ export default {
form_data.append('docname', this.docname);
}

if (this.fieldname) {
form_data.append('fieldname', this.fieldname);
}

if (this.method) {
form_data.append('method', this.method);
}


+ 2
- 0
frappe/public/js/frappe/file_uploader/index.js Целия файл

@@ -7,6 +7,7 @@ export default class FileUploader {
on_success,
doctype,
docname,
fieldname,
files,
folder,
restrictions,
@@ -28,6 +29,7 @@ export default class FileUploader {
show_upload_button: !Boolean(this.dialog),
doctype,
docname,
fieldname,
method,
folder,
on_success,


+ 1
- 0
frappe/public/js/frappe/form/controls/attach.js Целия файл

@@ -66,6 +66,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
if (this.frm) {
options.doctype = this.frm.doctype;
options.docname = this.frm.docname;
options.fieldname = this.df.fieldname;
}

if (this.df.options) {


+ 4
- 1
frappe/public/js/frappe/form/controls/text_editor.js Целия файл

@@ -1,5 +1,7 @@
import Quill from 'quill';
import ImageResize from 'quill-image-resize';

Quill.register('modules/imageResize', ImageResize);
const CodeBlockContainer = Quill.import('formats/code-block-container');
CodeBlockContainer.tagName = 'PRE';
Quill.register(CodeBlockContainer, true);
@@ -145,7 +147,8 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
return {
modules: {
toolbar: this.get_toolbar_options(),
table: true
table: true,
imageResize: {}
},
theme: 'snow'
};


+ 1
- 1
frappe/public/js/frappe/form/layout.js Целия файл

@@ -525,7 +525,7 @@ frappe.ui.form.Layout = Class.extend({
return;
}

var parent = this.frm ? this.frm.doc : null;
var parent = this.frm ? this.frm.doc : this.doc || null;

if(typeof(expression) === 'boolean') {
out = expression;


+ 1
- 1
frappe/public/js/frappe/ui/toolbar/about.js Целия файл

@@ -6,7 +6,7 @@ frappe.ui.misc.about = function() {
$(d.body).html(repl("<div>\
<p>"+__("Open Source Applications for the Web")+"</p> \
<p><i class='fa fa-globe fa-fw'></i>\
Website: <a href='https://frappe.io' target='_blank'>https://frappe.io</a></p>\
Website: <a href='https://frappeframework.com' target='_blank'>https://frappeframework.com</a></p>\
<p><i class='fa fa-github fa-fw'></i>\
Source: <a href='https://github.com/frappe' target='_blank'>https://github.com/frappe</a></p>\
<p><i class='fa fa-linkedin fa-fw'></i>\


+ 3
- 1
frappe/public/js/frappe/views/reports/query_report.js Целия файл

@@ -592,6 +592,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
this.render_summary(data.report_summary);
}

if (data.message && !data.prepared_report) this.show_status(data.message);

this.toggle_message(false);
if (data.result && data.result.length) {
this.prepare_report_data(data);
@@ -1041,7 +1043,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {

if (column.colIndex === index && !value) {
value = "Total";
column.fieldtype = "Data"; // avoid type issues for value if Date column
column = { fieldtype: "Data" }; // avoid type issues for value if Date column
} else if (in_list(["Currency", "Float"], column.fieldtype)) {
// proxy for currency and float
data = this.data[0];


+ 1
- 1
frappe/public/js/frappe/widgets/utils.js Целия файл

@@ -151,7 +151,7 @@ function shorten_number(number, country) {
for (const map of number_system) {
const condition = map.condition ? map.condition(x) : x >= map.divisor;
if (condition) {
return Math.round(number/map.divisor) + ' ' + map.symbol;
return (number/map.divisor).toFixed(2) + ' ' + map.symbol;
}
}
return number.toFixed();


+ 1
- 1
frappe/templates/base.html Целия файл

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<!-- Built on Frappe. https://frappe.io/ -->
<!-- Built on Frappe. https://frappeframework.com/ -->
<html lang="en">
<head>
<meta charset="utf-8">


+ 1
- 1
frappe/templates/includes/footer/footer_powered.html Целия файл

@@ -1 +1 @@
<a href="https://frappe.io?source=website_footer" target="_blank" class="text-muted">Built on Frappe</a>
<a href="https://frappeframework.com?source=website_footer" target="_blank" class="text-muted">Built on Frappe</a>

+ 10
- 0
frappe/tests/test_api.py Целия файл

@@ -67,9 +67,19 @@ class TestAPI(unittest.TestCase):
{"doctype": "Note", "public": True, "title": "get_value", "content": test_content},
])
self.assertEqual(server.get_value("Note", "content", {"title": "get_value"}).get('content'), test_content)
name = server.get_value("Note", "name", {"title": "get_value"}).get('name')

# test by name
self.assertEqual(server.get_value("Note", "content", name).get('content'), test_content)

self.assertRaises(FrappeException, server.get_value, "Note", "(select (password) from(__Auth) order by name desc limit 1)", {"title": "get_value"})

def test_get_single(self):
server = FrappeClient(get_url(), "Administrator", "admin", verify=False)
server.set_value('Website Settings', 'Website Settings', 'title_prefix', 'test-prefix')
self.assertEqual(server.get_value('Website Settings', 'title_prefix', 'Website Settings').get('title_prefix'), 'test-prefix')
self.assertEqual(server.get_value('Website Settings', 'title_prefix').get('title_prefix'), 'test-prefix')
frappe.db.set_value('Website Settings', None, 'title_prefix', '')

def test_update_doc(self):
server = FrappeClient(get_url(), "Administrator", "admin", verify=False)


+ 9
- 2
frappe/tests/test_safe_exec.py Целия файл

@@ -1,5 +1,5 @@
from __future__ import unicode_literals
import unittest
import unittest, frappe
from frappe.utils.safe_exec import safe_exec

class TestSafeExec(unittest.TestCase):
@@ -7,4 +7,11 @@ class TestSafeExec(unittest.TestCase):
self.assertRaises(ImportError, safe_exec, 'import os')

def test_internal_attributes(self):
self.assertRaises(SyntaxError, safe_exec, '().__class__.__call__')
self.assertRaises(SyntaxError, safe_exec, '().__class__.__call__')

def test_sql(self):
_locals = dict(out=None)
safe_exec('''out = frappe.db.sql("select name from tabDocType where name='DocType'")''', None, _locals)
self.assertEqual(_locals['out'][0][0], 'DocType')

self.assertRaises(frappe.PermissionError, safe_exec, 'frappe.db.sql("update tabToDo set description=NULL")')

+ 0
- 2
frappe/twofactor.py Целия файл

@@ -187,9 +187,7 @@ def process_2fa_for_otp_app(user, otp_secret, otp_issuer):
otp_setup_completed = False

verification_obj = {
'totp_uri': totp_uri,
'method': 'OTP App',
'qrcode': get_qr_svg_code(totp_uri),
'setup': otp_setup_completed
}
return verification_obj


+ 27
- 15
frappe/utils/backups.py Целия файл

@@ -29,7 +29,7 @@ class BackupGenerator:
"""
def __init__(self, db_name, user, password, backup_path_db=None, backup_path_files=None,
backup_path_private_files=None, db_host="localhost", db_port=None, verbose=False,
db_type='mariadb'):
db_type='mariadb', backup_path_conf=None):
global _verbose
self.db_host = db_host
self.db_port = db_port
@@ -37,10 +37,14 @@ class BackupGenerator:
self.db_type = db_type
self.user = user
self.password = password
self.backup_path_files = backup_path_files
self.backup_path_conf = backup_path_conf
self.backup_path_db = backup_path_db
self.backup_path_files = backup_path_files
self.backup_path_private_files = backup_path_private_files

if not self.db_type:
self.db_type = 'mariadb'

if not self.db_port and self.db_type == 'mariadb':
self.db_port = 3306
elif not self.db_port and self.db_type == 'postgres':
@@ -48,10 +52,23 @@ class BackupGenerator:

site = frappe.local.site or frappe.generate_hash(length=8)
self.site_slug = site.replace('.', '_')

self.verbose = verbose
self.setup_backup_directory()
_verbose = verbose

def setup_backup_directory(self):
specified = self.backup_path_db or self.backup_path_files or self.backup_path_private_files

if not specified:
backups_folder = get_backup_path()
if not os.path.exists(backups_folder):
os.makedirs(backups_folder)
else:
for file_path in [self.backup_path_files, self.backup_path_db, self.backup_path_private_files]:
dir = os.path.dirname(file_path)
os.makedirs(dir, exist_ok=True)


def get_backup(self, older_than=24, ignore_files=False, force=False):
"""
Takes a new dump if existing file is old
@@ -83,11 +100,14 @@ class BackupGenerator:

def set_backup_file_name(self):
#Generate a random name using today's date and a 8 digit random number
for_conf = self.todays_date + "-" + self.site_slug + "-site_config_backup.json"
for_db = self.todays_date + "-" + self.site_slug + "-database.sql.gz"
for_public_files = self.todays_date + "-" + self.site_slug + "-files.tar"
for_private_files = self.todays_date + "-" + self.site_slug + "-private-files.tar"
backup_path = get_backup_path()

if not self.backup_path_conf:
self.backup_path_conf = os.path.join(backup_path, for_conf)
if not self.backup_path_db:
self.backup_path_db = os.path.join(backup_path, for_db)
if not self.backup_path_files:
@@ -150,19 +170,11 @@ class BackupGenerator:
print('Backed up files', os.path.abspath(backup_path))

def copy_site_config(self):
site_config_backup_path = os.path.join(
get_backup_path(),
"{time_stamp}-{site_slug}-site_config_backup.json".format(
time_stamp=self.todays_date,
site_slug=self.site_slug))
site_config_backup_path = self.backup_path_conf
site_config_path = os.path.join(frappe.get_site_path(), "site_config.json")
site_config = {}
if os.path.exists(site_config_path):
site_config.update(frappe.get_file_json(site_config_path))
with open(site_config_backup_path, "w") as f:
f.write(json.dumps(site_config, indent=2))
f.flush()
self.site_config_backup_path = site_config_backup_path

with open(site_config_backup_path, "w") as n, open(site_config_path) as c:
n.write(c.read())

def take_dump(self):
import frappe.utils


+ 4
- 0
frappe/utils/file_manager.py Целия файл

@@ -406,6 +406,10 @@ def extract_images_from_html(doc, content):
doctype = doc.parenttype if doc.parent else doc.doctype
name = doc.parent or doc.name

if doc.doctype == "Comment":
doctype = doc.reference_doctype
name = doc.reference_name

# TODO fix this
file_url = save_file(filename, content, doctype, name, decode=True).get("file_url")
if not frappe.flags.has_dataurl:


+ 11
- 1
frappe/utils/safe_exec.py Целия файл

@@ -50,7 +50,7 @@ def get_safe_globals():
dict=dict,
_dict=frappe._dict,
frappe=frappe._dict(
flags=frappe.flags,
flags=frappe._dict(),
format=frappe.format_value,
format_value=frappe.format_value,
date_format=date_format,
@@ -71,6 +71,8 @@ def get_safe_globals():
msgprint=frappe.msgprint,
throw=frappe.throw,
sendmail = frappe.sendmail,
get_print = frappe.get_print,
attach_print = frappe.attach_print,

user=user,
get_fullname=frappe.utils.get_fullname,
@@ -114,6 +116,7 @@ def get_safe_globals():
get_single_value = frappe.db.get_single_value,
get_default = frappe.db.get_default,
escape = frappe.db.escape,
sql = read_sql
)

if frappe.response:
@@ -132,6 +135,13 @@ def get_safe_globals():

return out

def read_sql(query, *args, **kwargs):
'''a wrapper for frappe.db.sql to allow reads'''
if query.strip().split(None, 1)[0].lower() == 'select':
return frappe.db.sql(query, *args, **kwargs)
else:
raise frappe.PermissionError('Only SELECT SQL allowed in scripting')

def _getitem(obj, key):
# guard function for RestrictedPython
# allow any key to be accessed as long as it does not start with underscore


+ 2
- 1
frappe/website/doctype/blog_post/blog_post.json Целия файл

@@ -110,6 +110,7 @@
"depends_on": "eval:doc.content_type === 'Markdown'",
"fieldname": "content_md",
"fieldtype": "Markdown Editor",
"ignore_xss_filter": 1,
"label": "Content (Markdown)"
},
{
@@ -193,7 +194,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 5,
"modified": "2020-07-21 16:25:17.154911",
"modified": "2020-08-31 16:55:03.687862",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",


+ 1
- 1
frappe/website/doctype/web_form/web_form.json Целия файл

@@ -209,7 +209,7 @@
"label": "Client Script"
},
{
"description": "For help see <a href=\"https://frappe.io/docs/user/en/guides/portal-development/web-forms\" target=\"_blank\">Client Script API and Examples</a>",
"description": "For help see <a href=\"https://frappeframework.com/docs/user/en/guides/portal-development/web-forms\" target=\"_blank\">Client Script API and Examples</a>",
"fieldname": "client_script",
"fieldtype": "Code",
"label": "Client Script"


+ 2
- 1
frappe/website/doctype/web_page/web_page.json Целия файл

@@ -126,6 +126,7 @@
"depends_on": "eval:doc.content_type==='Markdown'",
"fieldname": "main_section_md",
"fieldtype": "Markdown Editor",
"ignore_xss_filter": 1,
"label": "Main Section (Markdown)"
},
{
@@ -294,7 +295,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 20,
"modified": "2020-08-07 10:55:54.885448",
"modified": "2020-08-31 16:55:52.015249",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",


+ 2
- 2
frappe/website/render.py Целия файл

@@ -149,8 +149,8 @@ def add_preload_headers(response):
preload.append(("style", elem.get("href")))

links = []
for type, link in preload:
links.append("</{}>; rel=preload; as={}".format(link.lstrip("/"), type))
for _type, link in preload:
links.append("<{}>; rel=preload; as={}".format(link, _type))

if links:
response.headers["Link"] = ",".join(links)


+ 3
- 2
package.json Целия файл

@@ -17,7 +17,7 @@
"bugs": {
"url": "https://github.com/frappe/frappe/issues"
},
"homepage": "https://frappe.io",
"homepage": "https://frappeframework.com",
"dependencies": {
"ace-builds": "^1.4.8",
"air-datepicker": "http://github.com/frappe/air-datepicker",
@@ -39,10 +39,11 @@
"moment-timezone": "^0.5.28",
"quagga": "^0.12.1",
"quill": "2.0.0-dev.4",
"quill-image-resize": "^3.0.9",
"qz-tray": "^2.0.8",
"redis": "^2.8.0",
"showdown": "^1.9.1",
"snyk": "^1.316.1",
"snyk": "^1.382.0",
"socket.io": "^2.3.0",
"superagent": "^3.8.2",
"touch": "^3.1.0",


+ 1
- 1
requirements.txt Целия файл

@@ -34,7 +34,7 @@ oauthlib==3.1.0
openpyxl==2.6.4
passlib==1.7.2
pdfkit==0.6.1
Pillow==6.2.2
Pillow==7.1.0
premailer==3.6.1
psycopg2-binary==2.8.4
pyasn1==0.4.8


+ 1640
- 490
yarn.lock
Файловите разлики са ограничени, защото са твърде много
Целия файл


Зареждане…
Отказ
Запис