diff --git a/frappe/core/doctype/custom_field/custom_field.js b/frappe/core/doctype/custom_field/custom_field.js index 040cb10616..ec4eea4809 100644 --- a/frappe/core/doctype/custom_field/custom_field.js +++ b/frappe/core/doctype/custom_field/custom_field.js @@ -47,9 +47,18 @@ cur_frm.fields_dict['dt'].get_query = function(doc, dt, dn) { } cur_frm.cscript.fieldtype = function(doc, dt, dn) { - if(doc.fieldtype == 'Link') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter name of the document you want this field to be linked to in Options.
Eg.: Customer'; - else if(doc.fieldtype == 'Select') cur_frm.fields_dict['options_help'].disp_area.innerHTML = 'Please enter values in Options, with each option on a new line.
Eg.: Field: Country
Options:
China
India
United States

'; - else cur_frm.fields_dict['options_help'].disp_area.innerHTML = ''; + if(doc.fieldtype == 'Link') { + cur_frm.fields_dict['options_help'].disp_area.innerHTML = + __('Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer'); + } else if(doc.fieldtype == 'Select') { + cur_frm.fields_dict['options_help'].disp_area.innerHTML = + __('Options for select. Each option on a new line. e.g.:
Option 1
Option 2
Option 3
'); + } else if(doc.fieldtype == 'Dynamic Link') { + cur_frm.fields_dict['options_help'].disp_area.innerHTML = + __('Fieldname which will be the DocType for this link field.'); + } else { + cur_frm.fields_dict['options_help'].disp_area.innerHTML = ''; + } } diff --git a/frappe/core/doctype/custom_field/custom_field.json b/frappe/core/doctype/custom_field/custom_field.json index 58be09b1c4..55c404c902 100644 --- a/frappe/core/doctype/custom_field/custom_field.json +++ b/frappe/core/doctype/custom_field/custom_field.json @@ -57,7 +57,7 @@ "no_copy": 0, "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Button\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "reqd": 1, "search_index": 0 @@ -257,7 +257,7 @@ ], "icon": "icon-glass", "idx": 1, - "modified": "2014-05-26 03:21:02.832530", + "modified": "2014-06-20 05:54:17.225853", "modified_by": "Administrator", "module": "Core", "name": "Custom Field", diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index b68a566d98..b1296d4056 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -34,7 +34,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", + "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime", "permlevel": 0, "reqd": 1, "search_index": 1 @@ -304,7 +304,7 @@ "in_dialog": 1, "issingle": 0, "istable": 1, - "modified": "2014-05-26 03:00:13.705058", + "modified": "2014-06-20 05:42:29.975498", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 963c7dbb93..26bdba1ab0 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -11,6 +11,7 @@ from frappe.utils import now, cint from frappe.model import no_value_fields from frappe.model.document import Document from frappe.model.db_schema import type_map +from frappe.core.doctype.property_setter.property_setter import make_property_setter class DocType(Document): def validate(self): @@ -106,6 +107,29 @@ class DocType(Document): else: frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) + def before_reload(self): + if not (self.issingle and self.istable): + self.preserve_naming_series_options_in_property_setter() + + def preserve_naming_series_options_in_property_setter(self): + """preserve naming_series as property setter if it does not exist""" + naming_series = self.get("fields", {"fieldname": "naming_series"}) + + if not naming_series: + return + + # check if atleast 1 record exists + if not (frappe.db.table_exists("tab" + self.name) and frappe.db.sql("select name from `tab{}` limit 1".format(self.name))): + return + + existing_property_setter = frappe.db.get_value("Property Setter", {"doc_type": self.name, + "property": "options", "field_name": "naming_series"}) + + if not existing_property_setter: + make_property_setter(self.name, "naming_series", "options", naming_series[0].options, "Text") + if naming_series[0].default: + make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text") + def export_doc(self): from frappe.modules.export_file import export_to_files export_to_files(record_list=[['DocType', self.name]]) @@ -157,6 +181,7 @@ class DocType(Document): def validate_fields_for_doctype(doctype): validate_fields(frappe.get_meta(doctype).get("fields")) +# this is separate because it is also called via custom field def validate_fields(fields): def check_illegal_characters(fieldname): for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$', @@ -200,6 +225,13 @@ def validate_fields(fields): if d.in_list_view and d.fieldtype!="Image" and (d.fieldtype in no_value_fields): frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx)) + def check_dynamic_link_options(d): + if d.fieldtype=="Dynamic Link": + doctype_pointer = filter(lambda df: df.fieldname==d.options, fields) + if not doctype_pointer or (doctype_pointer[0].fieldtype!="Link") \ + or (doctype_pointer[0].options!="DocType"): + frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) + for d in fields: if not d.permlevel: d.permlevel = 0 if not d.fieldname: @@ -208,6 +240,7 @@ def validate_fields(fields): check_unique_fieldname(d.fieldname) check_illegal_mandatory(d) check_link_table_options(d) + check_dynamic_link_options(d) check_hidden_and_mandatory(d) check_in_list_view(d) diff --git a/frappe/core/doctype/event/event.json b/frappe/core/doctype/event/event.json index ec5e6ca3ad..de46143f73 100644 --- a/frappe/core/doctype/event/event.json +++ b/frappe/core/doctype/event/event.json @@ -218,24 +218,26 @@ }, { "fieldname": "ref_type", - "fieldtype": "Data", + "fieldtype": "Link", "hidden": 0, "label": "Ref Type", "no_copy": 0, "oldfieldname": "ref_type", "oldfieldtype": "Data", + "options": "DocType", "permlevel": 0, - "read_only": 1, + "read_only": 0, "search_index": 0 }, { "fieldname": "ref_name", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", "hidden": 0, "label": "Ref Name", "no_copy": 0, "oldfieldname": "ref_name", "oldfieldtype": "Data", + "options": "ref_type", "permlevel": 0, "read_only": 1, "search_index": 0 @@ -244,7 +246,7 @@ "icon": "icon-calendar", "idx": 1, "in_create": 1, - "modified": "2014-05-27 03:49:10.612463", + "modified": "2014-06-20 06:40:05.415405", "modified_by": "Administrator", "module": "Core", "name": "Event", diff --git a/frappe/core/doctype/todo/todo.json b/frappe/core/doctype/todo/todo.json index 1139bf07bb..ec9e27e2b2 100644 --- a/frappe/core/doctype/todo/todo.json +++ b/frappe/core/doctype/todo/todo.json @@ -91,13 +91,14 @@ { "allow_on_submit": 0, "fieldname": "reference_type", - "fieldtype": "Data", + "fieldtype": "Link", "hidden": 0, "in_filter": 0, "label": "Reference Type", "no_copy": 0, "oldfieldname": "reference_type", "oldfieldtype": "Data", + "options": "DocType", "permlevel": 0, "print_hide": 0, "report_hide": 0, @@ -107,13 +108,14 @@ { "allow_on_submit": 0, "fieldname": "reference_name", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", "hidden": 0, "in_filter": 0, "label": "Reference Name", "no_copy": 0, "oldfieldname": "reference_name", "oldfieldtype": "Data", + "options": "reference_type", "permlevel": 0, "print_hide": 0, "report_hide": 0, @@ -158,7 +160,7 @@ "in_dialog": 0, "issingle": 0, "max_attachments": 0, - "modified": "2014-05-27 03:49:21.667888", + "modified": "2014-06-20 06:20:11.947183", "modified_by": "Administrator", "module": "Core", "name": "ToDo", diff --git a/frappe/core/page/user_permissions/user_permissions.js b/frappe/core/page/user_permissions/user_permissions.js index 893a8731f6..c35c5574b5 100644 --- a/frappe/core/page/user_permissions/user_permissions.js +++ b/frappe/core/page/user_permissions/user_permissions.js @@ -77,6 +77,22 @@ frappe.UserPermissions = Class.extend({ options: "[Select]" }); + if(user_roles.indexOf("System Manager")!==-1) { + me.download = me.wrapper.appframe.add_field({ + fieldname: "download", + label: __("Download"), + fieldtype: "Button", + icon: "icon-download" + }); + + me.upload = me.wrapper.appframe.add_field({ + fieldname: "upload", + label: __("Upload"), + fieldtype: "Button", + icon: "icon-upload" + }); + } + // bind change event $.each(me.filters, function(k, f) { f.$input.on("change", function() { @@ -90,9 +106,51 @@ frappe.UserPermissions = Class.extend({ }); me.set_from_route(); + me.setup_download_upload(); } }); }, + setup_download_upload: function() { + var me = this; + me.download.$input.on("click", function() { + window.location.href = frappe.urllib.get_base_url() + + "/api/method/frappe.core.page.user_permissions.user_permissions.get_user_permissions_csv"; + }); + + me.upload.$input.on("click", function() { + var d = new frappe.ui.Dialog({ + title: "Upload User Permissions", + fields: [ + { + fieldtype:"HTML", + options: '
    '+ + "
  1. "+__("Upload CSV file containing all user permissions in the same format as Download.")+"
  2. "+ + "
  3. "+__("Any existing permission will be deleted / overwritten.")+"
  4. "+ + '
' + }, + { + fieldtype:"Attach", fieldname:"attach", + } + ], + primary_action_label: __("Upload and Sync"), + primary_action: function() { + frappe.call({ + method:"frappe.core.page.user_permissions.user_permissions.import_user_permissions", + args: { + filedata: d.fields_dict.attach.get_value() + }, + callback: function(r) { + if(!r.exc) { + msgprint("Permissions Updated"); + d.hide(); + } + } + }); + } + }); + d.show(); + }) + }, get_link_names: function() { return this.options.link_fields; }, diff --git a/frappe/core/page/user_permissions/user_permissions.py b/frappe/core/page/user_permissions/user_permissions.py index 5ad93f7bff..e4722cc467 100644 --- a/frappe/core/page/user_permissions/user_permissions.py +++ b/frappe/core/page/user_permissions/user_permissions.py @@ -7,6 +7,8 @@ from frappe import _ import frappe.defaults import frappe.permissions from frappe.core.doctype.user.user import get_system_users +from frappe.utils.datautils import UnicodeWriter, read_csv_content_from_uploaded_file +from frappe.defaults import clear_default @frappe.whitelist() def get_users_and_links(): @@ -79,3 +81,28 @@ def get_doctypes_for_user_permissions(): return frappe.db.sql_list("""select name from tabDocType where ifnull(issingle,0)=0 and ifnull(istable,0)=0 {condition}""".format(condition=condition), tuple(values)) + +@frappe.whitelist() +def get_user_permissions_csv(): + out = [["User Permissions"], ["User", "Document Type", "Value"]] + out += [[a.parent, a.defkey, a.defvalue] for a in get_permissions()] + + csv = UnicodeWriter() + for row in out: + csv.writerow(row) + + frappe.response['result'] = str(csv.getvalue()) + frappe.response['type'] = 'csv' + frappe.response['doctype'] = "User Permissions" + +@frappe.whitelist() +def import_user_permissions(): + frappe.only_for("System Manager") + rows = read_csv_content_from_uploaded_file(ignore_encoding=True) + clear_default(parenttype="User Permission") + + if rows[0][0]!="User Permissions" and rows[1][0] != "User": + frappe.throw(frappe._("Please upload using the same template as download.")) + + for row in rows[2:]: + frappe.permissions.add_user_permission(row[1], row[2], row[0]) diff --git a/frappe/database.py b/frappe/database.py index c082aa0a74..721de5576f 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -485,6 +485,9 @@ class Database: def table_exists(self, tablename): return tablename in [d[0] for d in self.sql("show tables")] + def a_row_exists(self, doctype): + return self.sql("select name from `tab{doctype}` limit 1".format(doctype=doctype)) + def exists(self, dt, dn=None): if isinstance(dt, basestring): if dt!="DocType" and dt==dn: diff --git a/frappe/defaults.py b/frappe/defaults.py index 80997e4420..2077e46207 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -4,6 +4,9 @@ from __future__ import unicode_literals import frappe +# Note: DefaultValue records are identified by parenttype +# __default, __global or 'User Permission' + common_keys = ["__default", "__global"] def set_user_default(key, value, user=None, parenttype=None): diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 7c84c5e654..fd3d66e001 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -264,14 +264,21 @@ class BaseDocument(object): return "{}: {}".format(_(df.label), docname) invalid_links = [] - for df in self.meta.get_link_fields(): - doctype = df.options + for df in self.meta.get_link_fields() + self.meta.get("fields", + {"fieldtype":"Dynamic Link"}): - if not doctype: - frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) docname = self.get(df.fieldname) if docname: + if df.fieldtype=="Link": + doctype = df.options + if not doctype: + frappe.throw(_("Options not set for link field {0}").format(df.fieldname)) + else: + doctype = self.get(df.options) + if not doctype: + frappe.throw(_("{0} must be set first").format(self.meta.get_label(df.options))) + # MySQL is case insensitive. Preserve case of the original docname in the Link Field. value = frappe.db.get_value(doctype, docname) setattr(self, df.fieldname, value) diff --git a/frappe/model/db_schema.py b/frappe/model/db_schema.py index 488f623cea..7f2a013d8c 100644 --- a/frappe/model/db_schema.py +++ b/frappe/model/db_schema.py @@ -29,6 +29,7 @@ type_map = { ,'Text': ('text', '') ,'Data': ('varchar', '255') ,'Link': ('varchar', '255') + ,'Dynamic Link':('varchar', '255') ,'Password': ('varchar', '255') ,'Select': ('varchar', '255') ,'Read Only': ('varchar', '255') diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 56c334f33a..86214cb3fb 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -8,6 +8,7 @@ import frappe.model.meta import frappe.defaults from frappe.utils.file_manager import remove_all from frappe import _ +from rename_doc import dynamic_link_queries def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False): """ @@ -31,7 +32,16 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa remove_all(doctype, name) if doctype=="DocType": - if not for_reload: + if for_reload: + + try: + doc = frappe.get_doc(doctype, name) + except frappe.DoesNotExistError: + pass + else: + doc.run_method("before_reload") + + else: frappe.db.sql("delete from `tabCustom Field` where dt = %s", name) frappe.db.sql("delete from `tabCustom Script` where dt = %s", name) frappe.db.sql("delete from `tabProperty Setter` where doc_type = %s", name) @@ -48,6 +58,7 @@ def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reloa # check if links exist if not force: check_if_doc_is_linked(doc) + check_if_doc_is_dynamically_linked(doc) delete_from_table(doctype, name, ignore_doctypes, doc) @@ -106,3 +117,21 @@ def check_if_doc_is_linked(doc, method="Delete"): frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, doc.name, item.parent or item.name, item.parenttype if item.parent else link_dt), frappe.LinkExistsError) + +def check_if_doc_is_dynamically_linked(doc): + for query in dynamic_link_queries: + for df in frappe.db.sql(query, as_dict=True): + if frappe.get_meta(df.parent).issingle: + + # dynamic link in single doc + refdoc = frappe.get_singles_dict(df.parent) + if refdoc.get(df.options)==doc.doctype and refdoc.get(df.fieldname)==doc.name: + frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, + doc.name, df.parent, ""), frappe.LinkExistsError) + else: + + # dynamic link in table + for name in frappe.db.sql_list("""select name from `tab{parent}` where + {options}=%s and {fieldname}=%s""".format(**df), (doc.doctype, doc.name)): + frappe.throw(_("Cannot delete or cancel because {0} {1} is linked with {2} {3}").format(doc.doctype, + doc.name, df.parent, name), frappe.LinkExistsError) diff --git a/frappe/model/document.py b/frappe/model/document.py index 1ccc9d2aa9..37265cbd7e 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -159,7 +159,10 @@ class Document(BaseDocument): self.check_if_latest() self.set_parent_in_children() self.run_before_save_methods() - self._validate() + + if self._action != "cancel": + self._validate() + if self._action == "update_after_submit": self.validate_update_after_submit() @@ -491,7 +494,7 @@ class Document(BaseDocument): val1 = cint(val1) val2 = cint(val2) elif df.fieldtype in ("Data", "Text", "Small Text", "Long Text", - "Text Editor", "Select", "Link"): + "Text Editor", "Select", "Link", "Dynamic Link"): val1 = cstr(val1) val2 = cstr(val2) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 5201158d3b..b8a4237da8 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -33,6 +33,8 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F link_fields = get_link_fields(doctype) update_link_field_values(link_fields, old, new, doctype) + rename_dynamic_links(doctype, old, new) + if doctype=='DocType': rename_doctype(doctype, old, new, force) @@ -274,3 +276,27 @@ def update_parenttype_values(old, new): update `tab%s` set parenttype=%s where parenttype=%s""" % (doctype, '%s', '%s'), (new, old)) + +dynamic_link_queries = [ + """select parent, fieldname, options from tabDocField where fieldtype='Dynamic Link'""", + """select dt as parent, fieldname, options from `tabCustom Field` where fieldtype='Dynamic Link'""", +] + +def rename_dynamic_links(doctype, old, new): + for query in dynamic_link_queries: + for df in frappe.db.sql(query, as_dict=True): + + # dynamic link in single, just one value to check + if frappe.get_meta(df.parent).issingle: + refdoc = frappe.get_singles_dict(df.parent) + if refdoc.get(df.options)==doctype and refdoc.get(df.fieldname)==old: + + frappe.db.sql("""update tabSingles set value=%s where + field=%s and value=%s and doctype=%s""", (new, df.fieldname, old, df.parent)) + else: + # replace for each value where renamed + for to_change in frappe.db.sql_list("""select name from `tab{parent}` where + {options}=%s and {fieldname}=%s""".format(**df), (doctype, old)): + + frappe.db.sql("""update `tab{parent}` set {fieldname}=%s + where name=%s""".format(**df), (new, to_change)) diff --git a/frappe/patches.txt b/frappe/patches.txt index ad0ae5f54f..ebe3416402 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -43,3 +43,4 @@ execute:frappe.db.sql("""delete from `tabUserRole` where ifnull(parentfield, '') frappe.patches.v4_0.remove_user_owner_custom_field execute:frappe.delete_doc("DocType", "Website Template") execute:frappe.reload_doc('website', 'doctype', 'website_route') #2014-06-17 +execute:frappe.db.sql("""update `tabProperty Setter` set property_type='Text' where property in ('options', 'default')""") #2014-06-20 diff --git a/frappe/permissions.py b/frappe/permissions.py index b5165f2024..95208d97a7 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -37,12 +37,13 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True, user=None): if not role_permissions.get(ptype): return False - if doc and role_permissions["apply_user_permissions"].get(ptype): + if doc: if isinstance(doc, basestring): doc = frappe.get_doc(meta.name, doc) - if not user_has_permission(doc, verbose=verbose, user=user): - return False + if role_permissions["apply_user_permissions"].get(ptype): + if not user_has_permission(doc, verbose=verbose, user=user): + return False if not has_controller_permissions(doc, ptype, user=user): return False diff --git a/frappe/public/js/frappe/form/attachments.js b/frappe/public/js/frappe/form/attachments.js index 5dbdb62e28..c292416fa3 100644 --- a/frappe/public/js/frappe/form/attachments.js +++ b/frappe/public/js/frappe/form/attachments.js @@ -61,7 +61,7 @@ frappe.ui.form.Attachments = Class.extend({ }, add_attachment: function(attachment) { var file_name = attachment.file_name; - var file_url = attachment.file_url; + var file_url = this.get_file_url(attachment); var fileid = attachment.name; if (!file_name) { file_name = file_url; @@ -96,6 +96,18 @@ frappe.ui.form.Attachments = Class.extend({ $close.remove(); } }, + get_file_url: function(attachment) { + var file_url = attachment.file_url; + if (!file_url) { + if (attachment.file_name.indexOf('files/') === 0) { + file_url = '/' + attachment.file_name; + } + else { + file_url = '/files/' + attachment.file_name; + } + } + return encodeURI(file_url); + }, remove_attachment_by_filename: function(filename, callback) { this.remove_attachment(this.get_attachments()[filename], callback); }, diff --git a/frappe/public/js/frappe/form/comments.js b/frappe/public/js/frappe/form/comments.js index b981037ad0..265ce50dff 100644 --- a/frappe/public/js/frappe/form/comments.js +++ b/frappe/public/js/frappe/form/comments.js @@ -49,7 +49,7 @@ frappe.ui.form.Comments = Class.extend({ c.fullname = frappe.user_info(c.comment_by).fullname; c.comment = frappe.markdown(c.comment); - $(repl('
\ + $(repl('
\ \ \ \ diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 88e87f3bb8..57ca6476e7 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -1,7 +1,7 @@ // Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -frappe.ui.form.make_control = function(opts) { +frappe.ui.form.make_control = function (opts) { var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, ""); if(frappe.ui.form[control_class_name]) { return new frappe.ui.form[control_class_name](opts); @@ -538,7 +538,8 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({ }, set_label: function() { $(this.label_span).html(" "); - this.$input && this.$input.html(this.df.label); + this.$input && this.$input.html((this.df.icon ? + (' ') : "") + this.df.label); } }); @@ -551,7 +552,8 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({ .on("click", function() { me.onclick(); }); - this.$value = $('
\ + this.$value = $('
\ + \ \ ×
') .prependTo(me.input_area) @@ -802,32 +804,36 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ this.setup_buttons(); this.setup_autocomplete(); }, + get_options: function() { + return this.df.options; + }, setup_buttons: function() { var me = this; // magnifier - search this.$input_area.find(".btn-search").on("click", function() { + var doctype = me.get_options(); + if(!doctype) return; new frappe.ui.form.LinkSelector({ - doctype: me.df.options, + doctype: doctype, target: me, txt: me.get_value() }); }); // open - if(frappe.model.can_read(me.df.options)) { - this.$input_area.find(".btn-open").on("click", function() { - var value = me.get_value(); - if(value && me.df.options) frappe.set_route("Form", me.df.options, value); - }); - } else { - this.$input_area.find(".btn-open").remove(); - } + this.$input_area.find(".btn-open").on("click", function() { + var value = me.get_value(); + if(value && me.get_options()) + frappe.set_route("Form", me.get_options(), value); + }); // new - if(frappe.model.can_create(me.df.options)) { + if(this.df.fieldtype==="Dynamic Link" || frappe.model.can_create(me.df.options)) { this.$input_area.find(".btn-new").on("click", function() { - me.frm.new_doc(me.df.options, me); + var doctype = me.get_options(); + if(!doctype) return; + me.frm.new_doc(doctype, me); }); } else { this.$input_area.find(".btn-new").remove(); @@ -854,18 +860,20 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ this.$input.autocomplete({ minLength: 0, source: function(request, response) { - if (!me.$input.cache[me.df.options]) { - me.$input.cache[me.df.options] = {}; + var doctype = me.get_options(); + if(!doctype) return; + if (!me.$input.cache[doctype]) { + me.$input.cache[doctype] = {}; } - if (me.$input.cache[me.df.options][request.term]!=null) { + if (me.$input.cache[doctype][request.term]!=null) { // immediately show from cache - response(me.$input.cache[me.df.options][request.term]); + response(me.$input.cache[doctype][request.term]); } var args = { 'txt': request.term, - 'doctype': me.df.options, + 'doctype': doctype, }; me.set_custom_query(args); @@ -876,13 +884,13 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ no_spinner: true, args: args, callback: function(r) { - if(frappe.model.can_create(me.df.options)) { + if(frappe.model.can_create(doctype)) { r.results.push({ value: " " + __("Create a new {0}", [me.df.options]) + "", make_new: true }); }; - me.$input.cache[me.df.options][request.term] = r.results; + me.$input.cache[doctype][request.term] = r.results; response(r.results); }, }); @@ -901,10 +909,13 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ select: function(event, ui) { me.autocomplete_open = false; if(ui.item.make_new) { + var doctype = me.get_options(); + if(!doctype) return; + if (me.frm) { - me.frm.new_doc(me.df.options, me); + me.frm.new_doc(doctype, me); } else { - new_doc(me.df.options); + new_doc(doctype); } return false; } @@ -968,10 +979,21 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ return; } - this.frm.script_manager.validate_link_and_fetch(this.df, this.docname, value, callback); + this.frm.script_manager.validate_link_and_fetch(this.df, this.get_options(), + this.docname, value, callback); }, }); +frappe.ui.form.ControlDynamicLink = frappe.ui.form.ControlLink.extend({ + get_options: function() { + var options = frappe.model.get_value(this.df.parent, this.docname, this.df.options); + if(!options) { + msgprint(__("Please set {0} first", + [frappe.meta.get_docfield(this.df.parent, this.df.options, this.docname).label])); + } + return options; + }, +}); frappe.ui.form.ControlCode = frappe.ui.form.ControlText.extend({ make_input: function() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index e99158a1ab..8d03823db6 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -33,6 +33,7 @@ frappe.form.formatters = { return value ? "" : ""; }, Link: function(value, docfield, options) { + var doctype = docfield._options || docfield.options; if(options && options.for_print) return value; if(!value) @@ -40,13 +41,13 @@ frappe.form.formatters = { if(docfield && docfield.link_onclick) { return repl('%(value)s', {onclick: docfield.link_onclick.replace(/"/g, '"'), value:value}); - } else if(docfield && docfield.options) { + } else if(docfield && doctype) { return repl('%(icon)s%(label)s', { - doctype: encodeURIComponent(docfield.options), + doctype: encodeURIComponent(doctype), name: encodeURIComponent(value), label: value, icon: (options && options.no_icon) ? "" : - (' ') + (' ') }); } else { return value; @@ -119,11 +120,20 @@ frappe.form.formatters = { } frappe.form.get_formatter = function(fieldtype) { - if(!fieldtype) fieldtype = "Data"; + if(!fieldtype) + fieldtype = "Data"; return frappe.form.formatters[fieldtype.replace(/ /g, "")] || frappe.form.formatters.Data; } frappe.format = function(value, df, options, doc) { if(!df) df = {"fieldtype":"Data"}; - return frappe.form.get_formatter(df.fieldtype)(value, df, options, doc); + var fieldtype = df.fieldtype || "Data"; + + // format Dynamic Link as a Link + if(fieldtype==="Dynamic Link") { + fieldtype = "Link"; + df._options = doc ? doc[df.options] : null; + } + + return frappe.form.get_formatter(fieldtype)(value, df, options, doc); } diff --git a/frappe/public/js/frappe/form/infobar.js b/frappe/public/js/frappe/form/infobar.js index 62ba2b4801..66f7a93133 100644 --- a/frappe/public/js/frappe/form/infobar.js +++ b/frappe/public/js/frappe/form/infobar.js @@ -116,13 +116,27 @@ frappe.ui.form.InfoBar = Class.extend({ }, go_prev_next: function(prev) { - var me = this; + var me = this, + filters = null, + order_by = "modified desc", + doclistview = frappe.pages["List/" + me.frm.doctype]; + + // filters / order defined in listview + if(doclistview) { + filters = doclistview.doclistview.filter_list.get_filters(); + if(doclistview.doclistview.listview.order_by) { + order_by = doclistview.doclistview.listview.order_by; + } + } + return frappe.call({ method: "frappe.widgets.form.utils.get_next", args: { doctype: me.frm.doctype, - name: me.frm.docname, - prev: prev ? 1 : 0 + value: me.frm.doc[order_by.split(" ")[0]], + prev: prev ? 1 : 0, + filters: filters, + order_by: order_by }, callback: function(r) { if(r.message) diff --git a/frappe/public/js/frappe/form/script_manager.js b/frappe/public/js/frappe/form/script_manager.js index 93cd5c8663..bd17d0a519 100644 --- a/frappe/public/js/frappe/form/script_manager.js +++ b/frappe/public/js/frappe/form/script_manager.js @@ -80,7 +80,7 @@ frappe.ui.form.ScriptManager = Class.extend({ console.log("----- end of error message -----"); console.group && console.groupEnd(); }, - validate_link_and_fetch: function(df, docname, value, callback) { + validate_link_and_fetch: function(df, doctype, docname, value, callback) { var me = this; if(value) { @@ -94,7 +94,7 @@ frappe.ui.form.ScriptManager = Class.extend({ type: "GET", args: { 'value': value, - 'options': df.options, + 'options': doctype, 'fetch': fetch }, no_spinner: true, diff --git a/frappe/public/js/frappe/ui/filters.js b/frappe/public/js/frappe/ui/filters.js index 0e1b90752e..bb24e36412 100644 --- a/frappe/public/js/frappe/ui/filters.js +++ b/frappe/public/js/frappe/ui/filters.js @@ -255,7 +255,7 @@ frappe.ui.Filter = Class.extend({ if(df.fieldtype=='Check') { df.fieldtype='Select'; df.options='No\nYes'; - } else if(['Text','Small Text','Text Editor','Code','Tag','Comments'].indexOf(df.fieldtype)!=-1) { + } else if(['Text','Small Text','Text Editor','Code','Tag','Comments','Dynamic Link'].indexOf(df.fieldtype)!=-1) { df.fieldtype = 'Data'; } else if(df.fieldtype=='Link' && this.$w.find('.condition').val()!="=") { df.fieldtype = 'Data'; diff --git a/frappe/public/js/frappe/views/doclistview.js b/frappe/public/js/frappe/views/doclistview.js index e3a497e3f6..28cc7ad794 100644 --- a/frappe/public/js/frappe/views/doclistview.js +++ b/frappe/public/js/frappe/views/doclistview.js @@ -87,9 +87,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ this.make_help(); this.$page.find(".show_filters").css({"padding":"15px", "margin":"0px -15px"}); var me = this; - // this.$w.on("render-complete", function() { - // me.set_sidebar_height(); - // }); + this.$w.on("render-complete", function() { + if(me.data.length===1) { + frappe.set_route("Form", me.doctype, me.data[0].name); + } + }); }, set_sidebar_height: function() { diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index 04d0d1d2d4..9a2a8a8020 100644 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -200,7 +200,7 @@ desktop_template = """from frappe import _ def get_data(): return {{ - "{app_title}": {{ + "{app_name}": {{ "color": "{app_color}", "icon": "{app_icon}", "type": "module", diff --git a/frappe/utils/file_manager.py b/frappe/utils/file_manager.py index d660fa2277..bde7154259 100644 --- a/frappe/utils/file_manager.py +++ b/frappe/utils/file_manager.py @@ -68,6 +68,8 @@ def save_url(file_url, dt, dn): 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 @@ -144,12 +146,12 @@ def save_file_on_filesystem(fname, content, content_type=None): } def check_max_file_size(content): - max_file_size = conf.get('max_file_size') or 1000000 + max_file_size = conf.get('max_file_size') or 3145728 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 / 1000000.0), + max_file_size / 1048576), raise_exception=MaxFileSizeReachedError) return file_size diff --git a/frappe/widgets/form/utils.py b/frappe/widgets/form/utils.py index 2caa09cf2e..79c69a89aa 100644 --- a/frappe/widgets/form/utils.py +++ b/frappe/widgets/form/utils.py @@ -63,15 +63,31 @@ def add_comment(doc): return doc.as_dict() @frappe.whitelist() -def get_next(doctype, name, prev): +def get_next(doctype, value, prev, filters=None, order_by="modified desc"): import frappe.widgets.reportview prev = int(prev) - field = "`tab%s`.name" % doctype + sort_field, sort_order = order_by.split(" ") + + if not filters: filters = [] + if isinstance(filters, basestring): + filters = json.loads(filters) + + # condition based on sort order + condition = ">" if sort_order.lower()=="desc" else "<" + + # switch the condition + if prev: + condition = "<" if condition==">" else "<" + + # add condition for next or prev item + if not order_by[0] in [f[1] for f in filters]: + filters.append([doctype, sort_field, condition, value]) + res = frappe.widgets.reportview.execute(doctype, - fields = [field], - filters = [[doctype, "name", "<" if prev else ">", name]], - order_by = field + " " + ("desc" if prev else "asc"), + fields = ["name"], + filters = filters, + order_by = sort_field + " " + sort_order, limit_start=0, limit_page_length=1, as_list=True) if not res: