Merge branch 'master' into develop
This commit is contained in:
commit
94ea028db3
26 changed files with 5064 additions and 4365 deletions
|
|
@ -5,7 +5,7 @@
|
|||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 8,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ if sys.version[0] == '2':
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '11.1.13'
|
||||
__version__ = '11.1.14'
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
local = Local()
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ def authenticate(user):
|
|||
@frappe.whitelist()
|
||||
def get(user, fields = None):
|
||||
duser = frappe.get_doc('User', user)
|
||||
|
||||
if frappe.db.exists('Chat Profile', user):
|
||||
dprof = frappe.get_doc('Chat Profile', user)
|
||||
|
||||
# If you're adding something here, make sure the client recieves it.
|
||||
|
|
|
|||
|
|
@ -677,6 +677,39 @@
|
|||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 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",
|
||||
"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": "Fetch If Empty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
|
|
@ -1551,7 +1584,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-12-19 18:31:44.809413",
|
||||
"modified": "2019-02-26 21:49:13.665322",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "DocField",
|
||||
|
|
@ -1565,4 +1598,4 @@
|
|||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,9 @@ class RolePermissionforPageandReport(Document):
|
|||
|
||||
def update_disable_prepared_report(self):
|
||||
if self.report:
|
||||
frappe.db.set_value('Report', self.report, 'disable_prepared_report', self.disable_prepared_report)
|
||||
# intentionally written update query in frappe.db.sql instead of frappe.db.set_value
|
||||
frappe.db.sql(""" update `tabReport` set disable_prepared_report = %s
|
||||
where name = %s""", (self.disable_prepared_report, self.report))
|
||||
|
||||
def get_args(self, row=None):
|
||||
name = self.page if self.set_role_for == 'Page' else self.report
|
||||
|
|
|
|||
|
|
@ -1048,7 +1048,7 @@ def update_roles(role_profile):
|
|||
user.set('roles', [])
|
||||
user.add_roles(*roles)
|
||||
|
||||
def create_contact(user, ignore_links=False):
|
||||
def create_contact(user, ignore_links=False, ignore_mandatory=False):
|
||||
if user.name in ["Administrator", "Guest"]: return
|
||||
|
||||
if not frappe.db.get_value("Contact", {"email_id": user.email}):
|
||||
|
|
@ -1061,7 +1061,7 @@ def create_contact(user, ignore_links=False):
|
|||
"gender": user.gender,
|
||||
"phone": user.phone,
|
||||
"mobile_no": user.mobile_no
|
||||
}).insert(ignore_permissions=True, ignore_links=ignore_links)
|
||||
}).insert(ignore_permissions=True, ignore_links=ignore_links, ignore_mandatory=ignore_mandatory)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "dt",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
|
|
@ -55,6 +56,7 @@
|
|||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -88,6 +90,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "label_help",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
|
|
@ -120,6 +123,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -155,6 +159,7 @@
|
|||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"description": "Select the label after which you want to insert new field.",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "insert_after",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
|
@ -188,6 +193,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -220,6 +226,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Data",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
|
@ -256,6 +263,7 @@
|
|||
"columns": 0,
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
|
@ -289,6 +297,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -322,6 +331,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -354,6 +364,41 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fetch If Empty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "options_help",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
|
|
@ -386,6 +431,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -418,6 +464,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -451,6 +498,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
|
|
@ -483,6 +531,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -516,6 +565,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
|
|
@ -547,6 +597,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -583,6 +634,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
|
|
@ -616,6 +668,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -650,6 +703,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Number of columns for a field in a List View or a Grid (Total Columns should be less than 11)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
|
|
@ -682,6 +736,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "properties",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -716,6 +771,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -749,6 +805,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -781,6 +838,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -813,6 +871,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.fieldtype===\"Link\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -844,6 +903,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -875,6 +935,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -909,6 +970,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -941,6 +1003,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
|
|
@ -972,6 +1035,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "no_copy",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1005,6 +1069,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1038,6 +1103,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1069,6 +1135,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1102,6 +1169,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1134,6 +1202,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1166,6 +1235,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1199,6 +1269,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "search_index",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
|
|
@ -1231,6 +1302,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Don't HTML Encode HTML tags like <script> or just characters like < or >, as they could be intentionally used in this field",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ignore_xss_filter",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1265,6 +1337,7 @@
|
|||
"columns": 0,
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1302,7 +1375,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-12-19 18:34:46.031246",
|
||||
"modified": "2019-02-26 21:30:12.326576",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Custom Field",
|
||||
|
|
@ -1356,4 +1429,4 @@
|
|||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ docfield_properties = {
|
|||
'fieldtype': 'Select',
|
||||
'options': 'Text',
|
||||
'fetch_from': 'Small Text',
|
||||
'fetch_if_empty': 'Check',
|
||||
'permlevel': 'Int',
|
||||
'width': 'Data',
|
||||
'print_width': 'Data',
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "label_and_type",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -52,6 +53,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "label",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -86,6 +88,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Data",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fieldtype",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
|
@ -120,6 +123,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -153,6 +157,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "reqd",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -188,6 +193,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "unique",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -220,6 +226,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_list_view",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -251,6 +258,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_standard_filter",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -284,6 +292,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:([\"Data\", \"Select\", \"Table\", \"Text\", \"Text Editor\", \"Link\", \"Small Text\", \"Long Text\", \"Read Only\", \"Heading\", \"Dynamic Link\"].indexOf(doc.fieldtype) !== -1)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_global_search",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -316,6 +325,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "bold",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -350,6 +360,7 @@
|
|||
"columns": 0,
|
||||
"default": "1",
|
||||
"depends_on": "eval:['Data', 'Select', 'Text', 'Small Text', 'Text Editor'].includes(doc.fieldtype)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "translatable",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -382,6 +393,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_7",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -415,6 +427,7 @@
|
|||
"columns": 0,
|
||||
"depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
|
||||
"description": "Set non-standard precision for a Float or Currency field",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "precision",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
|
|
@ -449,6 +462,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list(['Data', 'Link', 'Dynamic Link', 'Password', 'Select', 'Read Only', 'Attach', 'Attach Image'], doc.fieldtype)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
|
|
@ -482,6 +496,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "options",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -515,6 +530,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fetch_from",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -547,6 +563,41 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "If checked, this field will be not overwritten based on Fetch From if a value already exists.",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "fetch_if_empty",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fetch If Empty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "permissions",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -580,6 +631,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"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>18",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "depends_on",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
|
|
@ -615,6 +667,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "permlevel",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
|
|
@ -648,6 +701,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "hidden",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -683,6 +737,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "read_only",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -716,6 +771,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "collapsible",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -749,6 +805,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.fieldtype == \"Table\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "allow_bulk_edit",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -782,6 +839,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.fieldtype==\"Section Break\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "collapsible_depends_on",
|
||||
"fieldtype": "Code",
|
||||
"hidden": 0,
|
||||
|
|
@ -815,6 +873,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -846,6 +905,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "ignore_user_permissions",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -877,6 +937,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "allow_on_submit",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -910,6 +971,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "report_hide",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -944,6 +1006,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:(doc.fieldtype == 'Link')",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "remember_last_selected_value",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -976,6 +1039,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "display",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -1008,6 +1072,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -1041,6 +1106,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "in_filter",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1076,6 +1142,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
|
|
@ -1107,6 +1174,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
|
|
@ -1142,6 +1210,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "print_hide",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1176,6 +1245,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:[\"Int\", \"Float\", \"Currency\", \"Percent\"].indexOf(doc.fieldtype)!==-1",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "print_hide_if_no_value",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
|
|
@ -1209,6 +1279,7 @@
|
|||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Print Width of the field, if the field is a column in a table",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "print_width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -1244,6 +1315,7 @@
|
|||
"columns": 0,
|
||||
"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)",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "columns",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
|
|
@ -1276,6 +1348,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
|
|
@ -1311,6 +1384,7 @@
|
|||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "is_custom_field",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
|
|
@ -1347,7 +1421,7 @@
|
|||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-12-19 18:32:06.435085",
|
||||
"modified": "2019-02-26 21:25:17.487129",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Custom",
|
||||
"name": "Customize Form Field",
|
||||
|
|
@ -1361,4 +1435,4 @@
|
|||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
|
@ -46,3 +46,31 @@ class TestToDo(unittest.TestCase):
|
|||
|
||||
self.assertEqual(todo.assigned_by_full_name,
|
||||
frappe.db.get_value('User', todo.assigned_by, 'full_name'))
|
||||
|
||||
def test_fetch_if_empty(self):
|
||||
frappe.db.sql('delete from tabToDo')
|
||||
|
||||
# Allow user changes
|
||||
todo_meta = frappe.get_doc('DocType', 'ToDo')
|
||||
field = todo_meta.get('fields', dict(fieldname='assigned_by_full_name'))[0]
|
||||
field.fetch_from = 'assigned_by.full_name'
|
||||
field.fetch_if_empty = 1
|
||||
todo_meta.save()
|
||||
|
||||
frappe.clear_cache(doctype='ToDo')
|
||||
|
||||
todo = frappe.get_doc(dict(doctype='ToDo', description='test todo',
|
||||
assigned_by='Administrator', assigned_by_full_name='Admin')).insert()
|
||||
|
||||
self.assertEqual(todo.assigned_by_full_name, 'Admin')
|
||||
|
||||
# Overwrite user changes
|
||||
todo_meta = frappe.get_doc('DocType', 'ToDo')
|
||||
todo_meta.get('fields', dict(fieldname='assigned_by_full_name'))[0].fetch_if_empty = 0
|
||||
todo_meta.save()
|
||||
|
||||
todo.reload()
|
||||
todo.save()
|
||||
|
||||
self.assertEqual(todo.assigned_by_full_name,
|
||||
frappe.db.get_value('User', todo.assigned_by, 'full_name'))
|
||||
|
|
|
|||
|
|
@ -459,9 +459,12 @@ class BaseDocument(object):
|
|||
# that are mapped as link_fieldname.source_fieldname in Options of
|
||||
# Readonly or Data or Text type fields
|
||||
|
||||
# NOTE: All fields will be replaced, if you want manual changes to stay
|
||||
# use `frm.add_fetch`
|
||||
fields_to_fetch = self.meta.get_fields_to_fetch(df.fieldname)
|
||||
fields_to_fetch = [
|
||||
_df for _df in self.meta.get_fields_to_fetch(df.fieldname)
|
||||
if
|
||||
not _df.get('fetch_if_empty')
|
||||
or (_df.get('fetch_if_empty') and not self.get(_df.fieldname))
|
||||
]
|
||||
|
||||
if not fields_to_fetch:
|
||||
# cache a single value type
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ class DatabaseQuery(object):
|
|||
except ValueError:
|
||||
self.fields = [f.strip() for f in self.fields.split(",")]
|
||||
|
||||
# remove empty strings / nulls in fields
|
||||
self.fields = [f for f in self.fields if f]
|
||||
|
||||
for filter_name in ["filters", "or_filters"]:
|
||||
filters = getattr(self, filter_name)
|
||||
if isinstance(filters, string_types):
|
||||
|
|
|
|||
|
|
@ -1217,7 +1217,7 @@ class Document(BaseDocument):
|
|||
|
||||
if file_lock.lock_exists(self.get_signature()):
|
||||
frappe.throw(_('This document is currently queued for execution. Please try again'),
|
||||
title=_('Document Queued'), indicator='red')
|
||||
title=_('Document Queued'))
|
||||
|
||||
self.lock()
|
||||
enqueue('frappe.model.document.execute_action', doctype=self.doctype, name=self.name,
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ class Meta(Document):
|
|||
are to be fetched and updated for a particular link field
|
||||
|
||||
These fields are of type Data, Link, Text, Readonly and their
|
||||
options property is set as `link_fieldname`.`source_fieldname`'''
|
||||
fetch_from property is set as `link_fieldname`.`source_fieldname`'''
|
||||
|
||||
out = []
|
||||
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@ def execute():
|
|||
user.first_name = re.sub("[<>]+", '', frappe.safe_decode(user.first_name))
|
||||
if user.last_name:
|
||||
user.last_name = re.sub("[<>]+", '', frappe.safe_decode(user.last_name))
|
||||
create_contact(user, ignore_links=True)
|
||||
create_contact(user, ignore_links=True, ignore_mandatory=True)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,25 @@ Table.create = (value) => {
|
|||
}
|
||||
Quill.register(Table, true);
|
||||
|
||||
// link without href
|
||||
var Link = Quill.import('formats/link');
|
||||
|
||||
class MyLink extends Link {
|
||||
static create(value) {
|
||||
let node = super.create(value);
|
||||
value = this.sanitize(value);
|
||||
node.setAttribute('href', value);
|
||||
if(value.startsWith('/') || value.indexOf(window.location.host)) {
|
||||
// no href if internal link
|
||||
node.removeAttribute('target');
|
||||
}
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
Quill.register(MyLink);
|
||||
|
||||
|
||||
// hidden blot
|
||||
class HiddenBlock extends Block {
|
||||
static create(value) {
|
||||
|
|
@ -44,13 +63,11 @@ Uploader.DEFAULTS.mimetypes.push('image/gif');
|
|||
// inline style
|
||||
const BackgroundStyle = Quill.import('attributors/style/background');
|
||||
const ColorStyle = Quill.import('attributors/style/color');
|
||||
const SizeStyle = Quill.import('attributors/style/size');
|
||||
const FontStyle = Quill.import('attributors/style/font');
|
||||
const AlignStyle = Quill.import('attributors/style/align');
|
||||
const DirectionStyle = Quill.import('attributors/style/direction');
|
||||
Quill.register(BackgroundStyle, true);
|
||||
Quill.register(ColorStyle, true);
|
||||
Quill.register(SizeStyle, true);
|
||||
Quill.register(FontStyle, true);
|
||||
Quill.register(AlignStyle, true);
|
||||
Quill.register(DirectionStyle, true);
|
||||
|
|
@ -140,6 +157,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({
|
|||
return [
|
||||
[{ 'header': [1, 2, 3, false] }],
|
||||
['bold', 'italic', 'underline'],
|
||||
[{ 'color': [] }, { 'background': [] }],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image'],
|
||||
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ frappe.ui.form.Timeline = class Timeline {
|
|||
}
|
||||
|
||||
prepare_timeline_item(c) {
|
||||
if(!c.sender) c.sender = c.owner;
|
||||
if(!c.sender) c.sender = c.owner || 'Guest';
|
||||
|
||||
if(c.sender && c.sender.indexOf("<")!==-1) {
|
||||
c.sender = c.sender.split("<")[1].split(">")[0];
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ $.extend(frappe.model, {
|
|||
for(var fid=0;fid<docfields.length;fid++) {
|
||||
var f = docfields[fid];
|
||||
if(!in_list(frappe.model.no_value_type, f.fieldtype) && doc[f.fieldname]==null) {
|
||||
var v = frappe.model.get_default_value(f, doc, parent_doc);
|
||||
var v = !f.depends_on || doc[f.depends_on] ? frappe.model.get_default_value(f, doc, parent_doc) : null;
|
||||
if(v) {
|
||||
if(in_list(["Int", "Check"], f.fieldtype))
|
||||
v = cint(v);
|
||||
|
|
|
|||
|
|
@ -77,13 +77,9 @@ frappe.ui.Tags = class {
|
|||
}
|
||||
|
||||
removeTag(label) {
|
||||
label = frappe.utils.xss_sanitise(label);
|
||||
if(this.tagsList.includes(label)) {
|
||||
let $tag = this.$ul.find(`.frappe-tag[data-tag-label="${label}"]`);
|
||||
|
||||
// Just don't remove tag, but also the li DOM.
|
||||
$tag.parent('.tags-list-item').remove();
|
||||
this.tagsList.splice(this.tagsList.indexOf(label), 1);
|
||||
|
||||
this.onTagRemove && this.onTagRemove(label);
|
||||
}
|
||||
}
|
||||
|
|
@ -119,6 +115,7 @@ frappe.ui.Tags = class {
|
|||
|
||||
$removeTag.on("click", () => {
|
||||
this.removeTag($removeTag.attr('data-tag-label'));
|
||||
$removeTag.closest('.tags-list-item').remove();
|
||||
});
|
||||
|
||||
if(this.onTagClick) {
|
||||
|
|
|
|||
|
|
@ -52,10 +52,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
this.secondary_action = {
|
||||
label: __('Refresh'),
|
||||
action: () => {
|
||||
if(this.execution_time > 2) {
|
||||
this.setup_progress_bar();
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
};
|
||||
|
|
@ -171,8 +168,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
|
|||
|
||||
setup_progress_bar() {
|
||||
let seconds_elapsed = 0;
|
||||
const execution_time = this.report_settings.execution_time < 10
|
||||
? 10 : this.report_settings.execution_time;
|
||||
const execution_time = this.report_settings.execution_time || 0;
|
||||
|
||||
if (execution_time < 5) return;
|
||||
|
||||
this.interval = setInterval(function() {
|
||||
seconds_elapsed += 1;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@
|
|||
}
|
||||
|
||||
.ql-editor {
|
||||
font-family: @font-stack;
|
||||
line-height: 1.6;
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.25em;
|
||||
|
|
|
|||
453
frappe/utils/file_manager.py
Normal file
453
frappe/utils/file_manager.py
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import os, base64, re, json
|
||||
import hashlib
|
||||
import mimetypes
|
||||
import io
|
||||
from frappe.utils import get_hook_method, get_files_path, random_string, encode, cstr, call_hook_method, cint
|
||||
from frappe import _
|
||||
from frappe import conf
|
||||
from copy import copy
|
||||
from six.moves.urllib.parse import unquote
|
||||
from six import text_type, PY2, string_types
|
||||
|
||||
|
||||
class MaxFileSizeReachedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def get_file_url(file_data_name):
|
||||
data = frappe.db.get_value("File", file_data_name, ["file_name", "file_url"], as_dict=True)
|
||||
return data.file_url or data.file_name
|
||||
|
||||
|
||||
def upload():
|
||||
# get record details
|
||||
dt = frappe.form_dict.doctype
|
||||
dn = frappe.form_dict.docname
|
||||
file_url = frappe.form_dict.file_url
|
||||
filename = frappe.form_dict.filename
|
||||
frappe.form_dict.is_private = cint(frappe.form_dict.is_private)
|
||||
|
||||
if not filename and not file_url:
|
||||
frappe.msgprint(_("Please select a file or url"),
|
||||
raise_exception=True)
|
||||
|
||||
file_doc = get_file_doc()
|
||||
|
||||
comment = {}
|
||||
if dt and dn:
|
||||
comment = frappe.get_doc(dt, dn).add_comment("Attachment",
|
||||
_("added {0}").format("<a href='{file_url}' target='_blank'>{file_name}</a>{icon}".format(**{
|
||||
"icon": ' <i class="fa fa-lock text-warning"></i>' \
|
||||
if file_doc.is_private else "",
|
||||
"file_url": file_doc.file_url.replace("#", "%23") \
|
||||
if file_doc.file_name else file_doc.file_url,
|
||||
"file_name": file_doc.file_name or file_doc.file_url
|
||||
})))
|
||||
|
||||
return {
|
||||
"name": file_doc.name,
|
||||
"file_name": file_doc.file_name,
|
||||
"file_url": file_doc.file_url,
|
||||
"is_private": file_doc.is_private,
|
||||
"comment": comment.as_dict() if comment else {}
|
||||
}
|
||||
|
||||
def get_file_doc(dt=None, dn=None, folder=None, is_private=None, df=None):
|
||||
'''returns File object (Document) from given parameters or form_dict'''
|
||||
r = frappe.form_dict
|
||||
|
||||
if dt is None: dt = r.doctype
|
||||
if dn is None: dn = r.docname
|
||||
if df is None: df = r.docfield
|
||||
if folder is None: folder = r.folder
|
||||
if is_private is None: is_private = r.is_private
|
||||
|
||||
if r.filedata:
|
||||
file_doc = save_uploaded(dt, dn, folder, is_private, df)
|
||||
|
||||
elif r.file_url:
|
||||
file_doc = save_url(r.file_url, r.filename, dt, dn, folder, is_private, df)
|
||||
|
||||
return file_doc
|
||||
|
||||
def save_uploaded(dt, dn, folder, is_private, df=None):
|
||||
fname, content = get_uploaded_content()
|
||||
if content:
|
||||
return save_file(fname, content, dt, dn, folder, is_private=is_private, df=df);
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
def save_url(file_url, filename, dt, dn, folder, is_private, df=None):
|
||||
# if not (file_url.startswith("http://") or file_url.startswith("https://")):
|
||||
# frappe.msgprint("URL must start with 'http://' or 'https://'")
|
||||
# return None, None
|
||||
|
||||
file_url = unquote(file_url)
|
||||
file_size = frappe.form_dict.file_size
|
||||
|
||||
f = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_url": file_url,
|
||||
"file_name": filename,
|
||||
"attached_to_doctype": dt,
|
||||
"attached_to_name": dn,
|
||||
"attached_to_field": df,
|
||||
"folder": folder,
|
||||
"file_size": file_size,
|
||||
"is_private": is_private
|
||||
})
|
||||
f.flags.ignore_permissions = True
|
||||
try:
|
||||
f.insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
return frappe.get_doc("File", f.duplicate_entry)
|
||||
return f
|
||||
|
||||
|
||||
def get_uploaded_content():
|
||||
# should not be unicode when reading a file, hence using frappe.form
|
||||
if 'filedata' in frappe.form_dict:
|
||||
if "," in frappe.form_dict.filedata:
|
||||
frappe.form_dict.filedata = frappe.form_dict.filedata.rsplit(",", 1)[1]
|
||||
frappe.uploaded_content = base64.b64decode(frappe.form_dict.filedata)
|
||||
frappe.uploaded_filename = frappe.form_dict.filename
|
||||
return frappe.uploaded_filename, frappe.uploaded_content
|
||||
else:
|
||||
frappe.msgprint(_('No file attached'))
|
||||
return None, None
|
||||
|
||||
def save_file(fname, content, dt, dn, folder=None, decode=False, is_private=0, df=None):
|
||||
if decode:
|
||||
if isinstance(content, text_type):
|
||||
content = content.encode("utf-8")
|
||||
|
||||
if b"," in content:
|
||||
content = content.split(b",")[1]
|
||||
content = base64.b64decode(content)
|
||||
|
||||
file_size = check_max_file_size(content)
|
||||
content_hash = get_content_hash(content)
|
||||
content_type = mimetypes.guess_type(fname)[0]
|
||||
fname = get_file_name(fname, content_hash[-6:])
|
||||
file_data = get_file_data_from_hash(content_hash, is_private=is_private)
|
||||
if not file_data:
|
||||
call_hook_method("before_write_file", file_size=file_size)
|
||||
|
||||
write_file_method = get_hook_method('write_file', fallback=save_file_on_filesystem)
|
||||
file_data = write_file_method(fname, content, content_type=content_type, is_private=is_private)
|
||||
file_data = copy(file_data)
|
||||
|
||||
file_data.update({
|
||||
"doctype": "File",
|
||||
"attached_to_doctype": dt,
|
||||
"attached_to_name": dn,
|
||||
"attached_to_field": df,
|
||||
"folder": folder,
|
||||
"file_size": file_size,
|
||||
"content_hash": content_hash,
|
||||
"is_private": is_private
|
||||
})
|
||||
|
||||
f = frappe.get_doc(file_data)
|
||||
f.flags.ignore_permissions = True
|
||||
try:
|
||||
f.insert()
|
||||
except frappe.DuplicateEntryError:
|
||||
return frappe.get_doc("File", f.duplicate_entry)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
def get_file_data_from_hash(content_hash, is_private=0):
|
||||
for name in frappe.db.sql_list("select name from `tabFile` where content_hash=%s and is_private=%s", (content_hash, is_private)):
|
||||
b = frappe.get_doc('File', name)
|
||||
return {k: b.get(k) for k in frappe.get_hooks()['write_file_keys']}
|
||||
return False
|
||||
|
||||
|
||||
def save_file_on_filesystem(fname, content, content_type=None, is_private=0):
|
||||
fpath = write_file(content, fname, is_private)
|
||||
|
||||
if is_private:
|
||||
file_url = "/private/files/{0}".format(fname)
|
||||
else:
|
||||
file_url = "/files/{0}".format(fname)
|
||||
|
||||
return {
|
||||
'file_name': os.path.basename(fpath),
|
||||
'file_url': file_url
|
||||
}
|
||||
|
||||
|
||||
def get_max_file_size():
|
||||
return conf.get('max_file_size') or 10485760
|
||||
|
||||
|
||||
def check_max_file_size(content):
|
||||
max_file_size = get_max_file_size()
|
||||
file_size = len(content)
|
||||
|
||||
if file_size > max_file_size:
|
||||
frappe.msgprint(_("File size exceeded the maximum allowed size of {0} MB").format(
|
||||
max_file_size / 1048576),
|
||||
raise_exception=MaxFileSizeReachedError)
|
||||
|
||||
return file_size
|
||||
|
||||
|
||||
def write_file(content, fname, is_private=0):
|
||||
"""write file to disk with a random name (to compare)"""
|
||||
file_path = get_files_path(is_private=is_private)
|
||||
|
||||
# create directory (if not exists)
|
||||
frappe.create_folder(file_path)
|
||||
# write the file
|
||||
if isinstance(content, text_type):
|
||||
content = content.encode()
|
||||
with open(os.path.join(file_path.encode('utf-8'), fname.encode('utf-8')), 'wb+') as f:
|
||||
f.write(content)
|
||||
|
||||
return get_files_path(fname, is_private=is_private)
|
||||
|
||||
|
||||
def remove_all(dt, dn, from_delete=False):
|
||||
"""remove all files in a transaction"""
|
||||
try:
|
||||
for fid in frappe.db.sql_list("""select name from `tabFile` where
|
||||
attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)):
|
||||
remove_file(fid, dt, dn, from_delete)
|
||||
except Exception as e:
|
||||
if e.args[0]!=1054: raise # (temp till for patched)
|
||||
|
||||
|
||||
def remove_file_by_url(file_url, doctype=None, name=None):
|
||||
if doctype and name:
|
||||
fid = frappe.db.get_value("File", {"file_url": file_url,
|
||||
"attached_to_doctype": doctype, "attached_to_name": name})
|
||||
else:
|
||||
fid = frappe.db.get_value("File", {"file_url": file_url})
|
||||
|
||||
if fid:
|
||||
return remove_file(fid)
|
||||
|
||||
|
||||
def remove_file(fid, attached_to_doctype=None, attached_to_name=None, from_delete=False):
|
||||
"""Remove file and File entry"""
|
||||
file_name = None
|
||||
if not (attached_to_doctype and attached_to_name):
|
||||
attached = frappe.db.get_value("File", fid,
|
||||
["attached_to_doctype", "attached_to_name", "file_name"])
|
||||
if attached:
|
||||
attached_to_doctype, attached_to_name, file_name = attached
|
||||
|
||||
ignore_permissions, comment = False, None
|
||||
if attached_to_doctype and attached_to_name and not from_delete:
|
||||
doc = frappe.get_doc(attached_to_doctype, attached_to_name)
|
||||
ignore_permissions = doc.has_permission("write") or False
|
||||
if frappe.flags.in_web_form:
|
||||
ignore_permissions = True
|
||||
if not file_name:
|
||||
file_name = frappe.db.get_value("File", fid, "file_name")
|
||||
comment = doc.add_comment("Attachment Removed", _("Removed {0}").format(file_name))
|
||||
|
||||
frappe.delete_doc("File", fid, ignore_permissions=ignore_permissions)
|
||||
|
||||
return comment
|
||||
|
||||
|
||||
def delete_file_data_content(doc, only_thumbnail=False):
|
||||
method = get_hook_method('delete_file_data_content', fallback=delete_file_from_filesystem)
|
||||
method(doc, only_thumbnail=only_thumbnail)
|
||||
|
||||
|
||||
def delete_file_from_filesystem(doc, only_thumbnail=False):
|
||||
"""Delete file, thumbnail from File document"""
|
||||
if only_thumbnail:
|
||||
delete_file(doc.thumbnail_url)
|
||||
else:
|
||||
delete_file(doc.file_url)
|
||||
delete_file(doc.thumbnail_url)
|
||||
|
||||
|
||||
def delete_file(path):
|
||||
"""Delete file from `public folder`"""
|
||||
if path:
|
||||
if ".." in path.split("/"):
|
||||
frappe.msgprint(_("It is risky to delete this file: {0}. Please contact your System Manager.").format(path))
|
||||
|
||||
parts = os.path.split(path.strip("/"))
|
||||
if parts[0]=="files":
|
||||
path = frappe.utils.get_site_path("public", "files", parts[-1])
|
||||
|
||||
else:
|
||||
path = frappe.utils.get_site_path("private", "files", parts[-1])
|
||||
|
||||
path = encode(path)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def get_file(fname):
|
||||
"""Returns [`file_name`, `content`] for given file name `fname`"""
|
||||
file_path = get_file_path(fname)
|
||||
|
||||
# read the file
|
||||
if PY2:
|
||||
with open(encode(file_path)) as f:
|
||||
content = f.read()
|
||||
else:
|
||||
with io.open(encode(file_path), mode='rb') as f:
|
||||
content = f.read()
|
||||
try:
|
||||
# for plain text files
|
||||
content = content.decode()
|
||||
except UnicodeDecodeError:
|
||||
# for .png, .jpg, etc
|
||||
pass
|
||||
|
||||
return [file_path.rsplit("/", 1)[-1], content]
|
||||
|
||||
|
||||
def get_file_path(file_name):
|
||||
"""Returns file path from given file name"""
|
||||
f = frappe.db.sql("""select file_url from `tabFile`
|
||||
where name=%s or file_name=%s""", (file_name, file_name))
|
||||
if f:
|
||||
file_name = f[0][0]
|
||||
|
||||
file_path = file_name
|
||||
|
||||
if "/" not in file_path:
|
||||
file_path = "/files/" + file_path
|
||||
|
||||
if file_path.startswith("/private/files/"):
|
||||
file_path = get_files_path(*file_path.split("/private/files/", 1)[1].split("/"), is_private=1)
|
||||
|
||||
elif file_path.startswith("/files/"):
|
||||
file_path = get_files_path(*file_path.split("/files/", 1)[1].split("/"))
|
||||
|
||||
else:
|
||||
frappe.throw(_("There is some problem with the file url: {0}").format(file_path))
|
||||
|
||||
return file_path
|
||||
|
||||
|
||||
def get_content_hash(content):
|
||||
if isinstance(content, text_type):
|
||||
content = content.encode()
|
||||
return hashlib.md5(content).hexdigest()
|
||||
|
||||
|
||||
def get_file_name(fname, optional_suffix):
|
||||
# convert to unicode
|
||||
fname = cstr(fname)
|
||||
|
||||
n_records = frappe.db.sql("select name from `tabFile` where file_name=%s", fname)
|
||||
if len(n_records) > 0 or os.path.exists(encode(get_files_path(fname))):
|
||||
f = fname.rsplit('.', 1)
|
||||
if len(f) == 1:
|
||||
partial, extn = f[0], ""
|
||||
else:
|
||||
partial, extn = f[0], "." + f[1]
|
||||
return '{partial}{suffix}{extn}'.format(partial=partial, extn=extn, suffix=optional_suffix)
|
||||
return fname
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_file(file_url):
|
||||
"""
|
||||
Download file using token and REST API. Valid session or
|
||||
token is required to download private files.
|
||||
|
||||
Method : GET
|
||||
Endpoint : frappe.utils.file_manager.download_file
|
||||
URL Params : file_name = /path/to/file relative to site path
|
||||
"""
|
||||
file_doc = frappe.get_doc("File", {"file_url":file_url})
|
||||
file_doc.check_permission("read")
|
||||
path = os.path.join(get_files_path(), os.path.basename(file_url))
|
||||
|
||||
with open(path, "rb") as fileobj:
|
||||
filedata = fileobj.read()
|
||||
frappe.local.response.filename = os.path.basename(file_url)
|
||||
frappe.local.response.filecontent = filedata
|
||||
frappe.local.response.type = "download"
|
||||
|
||||
def extract_images_from_doc(doc, fieldname):
|
||||
content = doc.get(fieldname)
|
||||
content = extract_images_from_html(doc, content)
|
||||
if frappe.flags.has_dataurl:
|
||||
doc.set(fieldname, content)
|
||||
|
||||
|
||||
def extract_images_from_html(doc, content):
|
||||
frappe.flags.has_dataurl = False
|
||||
|
||||
def _save_file(match):
|
||||
data = match.group(1)
|
||||
data = data.split("data:")[1]
|
||||
headers, content = data.split(",")
|
||||
|
||||
if "filename=" in headers:
|
||||
filename = headers.split("filename=")[-1]
|
||||
|
||||
# decode filename
|
||||
if not isinstance(filename, text_type):
|
||||
filename = text_type(filename, 'utf-8')
|
||||
else:
|
||||
mtype = headers.split(";")[0]
|
||||
filename = get_random_filename(content_type=mtype)
|
||||
|
||||
doctype = doc.parenttype if doc.parent else doc.doctype
|
||||
name = doc.parent or doc.name
|
||||
|
||||
# TODO fix this
|
||||
file_url = save_file(filename, content, doctype, name, decode=True).get("file_url")
|
||||
if not frappe.flags.has_dataurl:
|
||||
frappe.flags.has_dataurl = True
|
||||
|
||||
return '<img src="{file_url}"'.format(file_url=file_url)
|
||||
|
||||
if content:
|
||||
content = re.sub('<img[^>]*src\s*=\s*["\'](?=data:)(.*?)["\']', _save_file, content)
|
||||
|
||||
return content
|
||||
|
||||
|
||||
def get_random_filename(extn=None, content_type=None):
|
||||
if extn:
|
||||
if not extn.startswith("."):
|
||||
extn = "." + extn
|
||||
|
||||
elif content_type:
|
||||
extn = mimetypes.guess_extension(content_type)
|
||||
|
||||
return random_string(7) + (extn or "")
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def validate_filename(filename):
|
||||
from frappe.utils import now_datetime
|
||||
timestamp = now_datetime().strftime(" %Y-%m-%d %H:%M:%S")
|
||||
fname = get_file_name(filename, timestamp)
|
||||
return fname
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_attachments(doctype, name, attachments):
|
||||
'''Add attachments to the given DocType'''
|
||||
if isinstance(attachments, string_types):
|
||||
attachments = json.loads(attachments)
|
||||
# loop through attachments
|
||||
files =[]
|
||||
for a in attachments:
|
||||
if isinstance(a, string_types):
|
||||
attach = frappe.db.get_value("File", {"name":a}, ["file_name", "file_url", "is_private"], as_dict=1)
|
||||
# save attachments to new doc
|
||||
f = save_url(attach.file_url, attach.file_name, doctype, name, "Home/Attachments", attach.is_private)
|
||||
files.append(f)
|
||||
|
||||
return files
|
||||
|
|
@ -61,10 +61,17 @@ def handle_html(data):
|
|||
obj = HTML2Text()
|
||||
obj.ignore_links = True
|
||||
obj.body_width = 0
|
||||
|
||||
try:
|
||||
value = obj.handle(h)
|
||||
except Exception:
|
||||
# unable to parse html, send it raw
|
||||
return value
|
||||
|
||||
value = ", ".join(value.split(' \n'))
|
||||
value = " ".join(value.split('\n'))
|
||||
value = ", ".join(value.split('# '))
|
||||
|
||||
return value
|
||||
|
||||
def read_xlsx_file_from_attached_file(file_url=None, fcontent=None, filepath=None):
|
||||
|
|
|
|||
|
|
@ -153,8 +153,8 @@
|
|||
<!-- save/next button -->
|
||||
{% if (loop.index == layout|len or frappe.form_dict.new) %}
|
||||
{% if not read_only %}
|
||||
<button type="submit" class="btn btn-primary btn-sm btn-form-submit">
|
||||
{{ _(button_label or "Save") }}</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm btn-form-submit footer-button">
|
||||
{{ _("Save") }}</button>
|
||||
{% endif %}
|
||||
{% elif layout|len > 1 %}
|
||||
<button class="btn btn-primary btn-sm btn-change-section"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ frappe.ready(function() {
|
|||
setTimeout(() => {
|
||||
$('body').css('display', 'block');
|
||||
|
||||
// remove footer save button if form height is less than window height
|
||||
if($('.webform-wrapper').height() < window.innerHeight) {
|
||||
$(".footer-button").addClass("hide");
|
||||
}
|
||||
|
||||
if (frappe.init_client_script) {
|
||||
frappe.init_client_script();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,35 +13,34 @@ $.extend(frappe, {
|
|||
lang: 'en'
|
||||
},
|
||||
_assets_loaded: [],
|
||||
require: function(url, callback) {
|
||||
|
||||
let async = false;
|
||||
if (callback) {
|
||||
async = true;
|
||||
require: async function(links, callback) {
|
||||
if (typeof (links) === 'string') {
|
||||
links = [links];
|
||||
}
|
||||
for (let link of links) {
|
||||
await this.add_asset_to_head(link);
|
||||
}
|
||||
|
||||
if(frappe._assets_loaded.indexOf(url)!==-1) {
|
||||
callback && callback();
|
||||
return;
|
||||
}
|
||||
|
||||
return $.ajax({
|
||||
url: url,
|
||||
async: async,
|
||||
dataType: "text",
|
||||
success: function(data) {
|
||||
var el;
|
||||
if(url.split(".").splice(-1) == "js") {
|
||||
},
|
||||
add_asset_to_head(link) {
|
||||
return new Promise(resolve => {
|
||||
if (frappe._assets_loaded.includes(link)) return resolve();
|
||||
let el;
|
||||
if(link.split('.').pop() === 'js') {
|
||||
el = document.createElement('script');
|
||||
el.type = 'text/javascript';
|
||||
el.src = link;
|
||||
} else {
|
||||
el = document.createElement('style');
|
||||
el = document.createElement('link');
|
||||
el.type = 'text/css';
|
||||
el.rel = 'stylesheet';
|
||||
el.href = link;
|
||||
}
|
||||
el.appendChild(document.createTextNode(data));
|
||||
document.getElementsByTagName('head')[0].appendChild(el);
|
||||
frappe._assets_loaded.push(url);
|
||||
|
||||
callback && callback();
|
||||
}
|
||||
el.onload = () => {
|
||||
frappe._assets_loaded.push(link);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
},
|
||||
hide_message: function() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue