瀏覽代碼

New Control: Table MultiSelect (#6675)

* feat(control): Add Table MultiSelect control

* fix: Use btn-group instead of span

* fix: Remove functionality

* fix: Add 'Table MultiSelect' to Field doctypes

* fix: Replace usage of string 'Table' with array `table_fields`

* fix: Use internal array to store values instead of building from HTML elements

* fix(style): Add semicolon

* fix: Read only mode and click to navigate to form

* style: indent

* fix: fallback to empty array

* fix: Add formatters in js and py

* style: missing semicolon

* fix: Add docfield validation
version-14
Faris Ansari 6 年之前
committed by Nabin Hait
父節點
當前提交
3b86f16b73
共有 36 個文件被更改,包括 3041 次插入2827 次删除
  1. +2
    -1
      frappe/cache_manager.py
  2. +1471
    -1471
      frappe/core/doctype/docfield/docfield.json
  3. +21
    -6
      frappe/core/doctype/doctype/doctype.py
  4. +3
    -3
      frappe/core/doctype/version/version.py
  5. +2
    -2
      frappe/custom/doctype/custom_field/custom_field.json
  6. +1
    -1
      frappe/custom/doctype/customize_form/customize_form.py
  7. +1279
    -1279
      frappe/custom/doctype/customize_form_field/customize_form_field.json
  8. +10
    -6
      frappe/custom/doctype/property_setter/property_setter.py
  9. +6
    -5
      frappe/desk/form/linked_with.py
  10. +1
    -1
      frappe/desk/form/load.py
  11. +1
    -1
      frappe/integrations/doctype/webhook/webhook.js
  12. +2
    -1
      frappe/model/__init__.py
  13. +4
    -4
      frappe/model/base_document.py
  14. +8
    -2
      frappe/model/delete_doc.py
  15. +1
    -1
      frappe/model/docfield.py
  16. +3
    -3
      frappe/model/document.py
  17. +3
    -3
      frappe/model/mapper.py
  18. +3
    -3
      frappe/model/meta.py
  19. +20
    -10
      frappe/model/rename_doc.py
  20. +2
    -2
      frappe/model/utils/rename_field.py
  21. +2
    -1
      frappe/public/build.json
  22. +130
    -0
      frappe/public/js/frappe/form/controls/table_multiselect.js
  23. +10
    -0
      frappe/public/js/frappe/form/formatters.js
  24. +1
    -1
      frappe/public/js/frappe/form/script_manager.js
  25. +1
    -1
      frappe/public/js/frappe/model/create_new.js
  26. +3
    -3
      frappe/public/js/frappe/model/meta.js
  27. +8
    -4
      frappe/public/js/frappe/model/model.js
  28. +5
    -5
      frappe/public/js/frappe/model/sync.js
  29. +1
    -1
      frappe/public/js/frappe/ui/filters/field_select.js
  30. +1
    -1
      frappe/public/js/frappe/ui/filters/filters.js
  31. +1
    -1
      frappe/public/js/frappe/views/kanban/kanban_view.js
  32. +1
    -1
      frappe/public/js/legacy/client_script_helpers.js
  33. +3
    -1
      frappe/public/js/legacy/form.js
  34. +23
    -0
      frappe/public/less/controls.less
  35. +6
    -0
      frappe/utils/formatters.py
  36. +2
    -2
      frappe/utils/global_search.py

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

@@ -70,7 +70,8 @@ def clear_doctype_cache(doctype=None):

# clear all parent doctypes

for dt in frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=doctype)):
for dt in frappe.db.get_all('DocField', 'parent',
dict(fieldtype=['in', frappe.model.table_fields], options=doctype)):
clear_single(dt.parent)

# clear all notifications


+ 1471
- 1471
frappe/core/doctype/docfield/docfield.json
文件差異過大導致無法顯示
查看文件


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

@@ -10,7 +10,7 @@ import frappe
from frappe import _

from frappe.utils import now, cint
from frappe.model import no_value_fields, default_fields, data_fieldtypes
from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields
from frappe.model.document import Document
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.desk.notifications import delete_notification_count_for
@@ -82,7 +82,7 @@ class DocType(Document):
if not [d.fieldname for d in self.fields if d.in_list_view]:
cnt = 0
for d in self.fields:
if d.reqd and not d.hidden and not d.fieldtype == "Table":
if d.reqd and not d.hidden and not d.fieldtype in table_fields:
d.in_list_view = 1
cnt += 1
if cnt == 4: break
@@ -171,7 +171,8 @@ class DocType(Document):
"""Change the timestamp of parent DocType if the current one is a child to clear caches."""
if frappe.flags.in_import:
return
parent_list = frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=self.name))
parent_list = frappe.db.get_all('DocField', 'parent',
dict(fieldtype=['in', frappe.model.table_fields], options=self.name))
for p in parent_list:
frappe.db.sql('UPDATE `tabDocType` SET modified=%s WHERE `name`=%s', (now(), p.parent))

@@ -511,11 +512,11 @@ def validate_fields(meta):
validate_column_length(fieldname)

def check_illegal_mandatory(d):
if (d.fieldtype in no_value_fields) and d.fieldtype!="Table" and d.reqd:
if (d.fieldtype in no_value_fields) and d.fieldtype not in table_fields and d.reqd:
frappe.throw(_("Field {0} of type {1} cannot be mandatory").format(d.label, d.fieldtype))

def check_link_table_options(d):
if d.fieldtype in ("Link", "Table"):
if d.fieldtype in ("Link",) + table_fields:
if not d.options:
frappe.throw(_("Options required for Link or Table type field {0} in row {1}").format(d.label, d.idx))
if d.options=="[Select]" or d.options==d.parent:
@@ -692,6 +693,19 @@ def validate_fields(meta):
re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", depends_on):
frappe.throw(_("Invalid {0} condition").format(frappe.unscrub(field)), frappe.ValidationError)

def check_table_multiselect_option(docfield):
'''check if the doctype provided in Option has atleast 1 Link field'''
if not docfield.fieldtype == 'Table MultiSelect': return

doctype = docfield.options
meta = frappe.get_meta(doctype)
link_field = [df for df in meta.fields if df.fieldtype == 'Link']

if not link_field:
frappe.throw(_('DocType <b>{0}</b> provided for the field <b>{1}</b> must have atleast one Link field')
.format(doctype, docfield.fieldname), frappe.ValidationError)


fields = meta.get("fields")
fieldname_list = [d.fieldname for d in fields]

@@ -702,7 +716,7 @@ def validate_fields(meta):

for d in fields:
if not d.permlevel: d.permlevel = 0
if d.fieldtype != "Table": d.allow_bulk_edit = 0
if d.fieldtype not in table_fields: d.allow_bulk_edit = 0
if d.fieldtype == "Barcode": d.ignore_xss_filter = 1
if not d.fieldname:
d.fieldname = d.fieldname.lower()
@@ -719,6 +733,7 @@ def validate_fields(meta):
check_illegal_default(d)
check_unique_and_text(d)
check_illegal_depends_on_conditions(d)
check_table_multiselect_option(d)

check_fold(fields)
check_search_fields(meta, fields)


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

@@ -7,7 +7,7 @@ from __future__ import unicode_literals
import frappe, json

from frappe.model.document import Document
from frappe.model import no_value_fields
from frappe.model import no_value_fields, table_fields

class Version(Document):
def set_diff(self, old, new):
@@ -42,12 +42,12 @@ def get_diff(old, new, for_child=False):
}'''
out = frappe._dict(changed = [], added = [], removed = [], row_changed = [])
for df in new.meta.fields:
if df.fieldtype in no_value_fields and df.fieldtype != 'Table':
if df.fieldtype in no_value_fields and df.fieldtype not in table_fields:
continue

old_value, new_value = old.get(df.fieldname), new.get(df.fieldname)

if df.fieldtype=='Table':
if df.fieldtype in table_fields:
# make maps
old_row_by_name, new_row_by_name = {}, {}
for d in old_value:


+ 2
- 2
frappe/custom/doctype/custom_field/custom_field.json 查看文件

@@ -234,7 +234,7 @@
"no_copy": 0,
"oldfieldname": "fieldtype",
"oldfieldtype": "Select",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime\nSignature",
"options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
@@ -1302,7 +1302,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-23 19:56:43.328280",
"modified": "2018-12-19 18:34:46.031246",
"modified_by": "Administrator",
"module": "Custom",
"name": "Custom Field",


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

@@ -69,7 +69,7 @@ docfield_properties = {

allowed_fieldtype_change = (('Currency', 'Float', 'Percent'), ('Small Text', 'Data'),
('Text', 'Data'), ('Text', 'Text Editor', 'Code', 'Signature', 'HTML Editor'), ('Data', 'Select'),
('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation'))
('Text', 'Small Text'), ('Text', 'Data', 'Barcode'), ('Code', 'Geolocation'), ('Table', 'Table MultiSelect'))

allowed_fieldtype_for_options_change = ('Read Only', 'HTML', 'Select', 'Data')



+ 1279
- 1279
frappe/custom/doctype/customize_form_field/customize_form_field.json
文件差異過大導致無法顯示
查看文件


+ 10
- 6
frappe/custom/doctype/property_setter/property_setter.py 查看文件

@@ -37,12 +37,16 @@ class PropertySetter(Document):
and property = %(property)s""", self.get_valid_dict())

def get_property_list(self, dt):
return frappe.db.sql("""select fieldname, label, fieldtype
from tabDocField
where parent=%s
and fieldtype not in ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Table', 'Fold')
and coalesce(fieldname, '') != ''
order by label asc""", dt, as_dict=1)
return frappe.db.get_all('DocField',
fields=['fieldname', 'label', 'fieldtype'],
filters={
'parent': dt,
'fieldtype': ['not in', ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields],
'fieldname': ['!=', '']
},
order_by='label asc',
as_dict=1
)

def get_setup_data(self):
return {


+ 6
- 5
frappe/desk/form/linked_with.py 查看文件

@@ -42,9 +42,10 @@ def get_linked_docs(doctype, name, linkinfo=None, for_doctype=None):
link_meta_bundle = frappe.desk.form.load.get_meta_bundle(dt)
linkmeta = link_meta_bundle[0]
if not linkmeta.get("issingle"):
fields = [d.fieldname for d in linkmeta.get("fields", {"in_list_view":1,
"fieldtype": ["not in", ["Image", "HTML", "Button", "Table"]]})] \
+ ["name", "modified", "docstatus"]
fields = [d.fieldname for d in linkmeta.get("fields", {
"in_list_view": 1,
"fieldtype": ["not in", ("Image", "HTML", "Button") + frappe.model.table_fields]
})] + ["name", "modified", "docstatus"]

if link.get("add_fields"):
fields += link["add_fields"]
@@ -116,7 +117,7 @@ def _get_linked_doctypes(doctype, without_ignore_user_permissions_enabled=False)
ret.update(get_linked_fields(doctype, without_ignore_user_permissions_enabled))
ret.update(get_dynamic_linked_fields(doctype, without_ignore_user_permissions_enabled))

filters=[['fieldtype','=','Table'], ['options', '=', doctype]]
filters=[['fieldtype', 'in', frappe.model.table_fields], ['options', '=', doctype]]
if without_ignore_user_permissions_enabled: filters.append(['ignore_user_permissions', '!=', 1])
# find links of parents
links = frappe.get_all("DocField", fields=["parent as dt"], filters=filters)
@@ -159,7 +160,7 @@ def get_linked_fields(doctype, without_ignore_user_permissions_enabled=False):
for doctype_name in links_dict:
ret[doctype_name] = { "fieldname": links_dict.get(doctype_name) }
table_doctypes = frappe.get_all("DocType", filters=[["istable", "=", "1"], ["name", "in", tuple(links_dict)]])
child_filters = [['fieldtype','=', 'Table'], ['options', 'in', tuple(doctype.name for doctype in table_doctypes)]]
child_filters = [['fieldtype','in', frappe.model.table_fields], ['options', 'in', tuple(doctype.name for doctype in table_doctypes)]]
if without_ignore_user_permissions_enabled: child_filters.append(['ignore_user_permissions', '!=', 1])

# find out if linked in a child table


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

@@ -80,7 +80,7 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None):
def get_meta_bundle(doctype):
bundle = [frappe.desk.form.meta.get_meta(doctype)]
for df in bundle[0].fields:
if df.fieldtype=="Table":
if df.fieldtype in frappe.model.table_fields:
bundle.append(frappe.desk.form.meta.get_meta(df.options, not frappe.conf.developer_mode))
return bundle



+ 1
- 1
frappe/integrations/doctype/webhook/webhook.js 查看文件

@@ -8,7 +8,7 @@ frappe.webhook = {
frappe.model.with_doctype(doc.webhook_doctype, function() {
var fields = $.map(frappe.get_doc("DocType", frm.doc.webhook_doctype).fields, function(d) {
if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
d.fieldtype === 'Table') {
frappe.model.table_fields.includes(d.fieldtype)) {
return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
}
else if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') {


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

@@ -37,12 +37,13 @@ data_fieldtypes = (
'Geolocation'
)

no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Button', 'Image',
no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Table MultiSelect', 'Button', 'Image',
'Fold', 'Heading')
display_fieldtypes = ('Section Break', 'Column Break', 'HTML', 'Button', 'Image', 'Fold', 'Heading')
default_fields = ('doctype','name','owner','creation','modified','modified_by',
'parent','parentfield','parenttype','idx','docstatus')
optional_fields = ("_user_tags", "_comments", "_assign", "_liked_by", "_seen")
table_fields = ('Table', 'Table MultiSelect')

def delete_fields(args_dict, delete=0):
"""


+ 4
- 4
frappe/model/base_document.py 查看文件

@@ -7,7 +7,7 @@ from six import iteritems, string_types
import frappe
import datetime
from frappe import _
from frappe.model import default_fields
from frappe.model import default_fields, table_fields
from frappe.model.naming import set_new_name
from frappe.model.utils.link_count import notify_link_count
from frappe.modules import load_doctype_module
@@ -222,7 +222,7 @@ class BaseDocument(object):
# unique empty field should be set to None
d[fieldname] = None

if isinstance(d[fieldname], list) and df.fieldtype != 'Table':
if isinstance(d[fieldname], list) and df.fieldtype not in table_fields:
frappe.throw(_('Value for {0} cannot be a list').format(_(df.label)))

if convert_dates_to_str and isinstance(d[fieldname], (datetime.datetime, datetime.time, datetime.timedelta)):
@@ -398,7 +398,7 @@ class BaseDocument(object):
def _get_missing_mandatory_fields(self):
"""Get mandatory fields that do not have any values"""
def get_msg(df):
if df.fieldtype == "Table":
if df.fieldtype in table_fields:
return "{}: {}: {}".format(_("Error"), _("Data missing in table"), _(df.label))

elif self.parentfield:
@@ -573,7 +573,7 @@ class BaseDocument(object):
db_value = db_values.get(key)

if df and not df.allow_on_submit and (self.get(key) or db_value):
if df.fieldtype=="Table":
if df.fieldtype in table_fields:
# just check if the table size has changed
# individual fields will be checked in the loop for children
self_value = len(self.get(key))


+ 8
- 2
frappe/model/delete_doc.py 查看文件

@@ -141,8 +141,14 @@ def delete_from_table(doctype, name, ignore_doctypes, doc):

else:
def get_table_fields(field_doctype):
return frappe.db.sql_list("""select options from `tab{}` where fieldtype='Table'
and parent=%s""".format(field_doctype), doctype)
return [r[0] for r in frappe.get_all(field_doctype,
fields='options',
filters={
'fieldtype': ['in', frappe.model.table_fields],
'parent': doctype
},
as_list=1
)]

tables = get_table_fields("DocField")
if not frappe.flags.in_install=="frappe":


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

@@ -36,7 +36,7 @@ def update_table(f, new):

def update_parent_field(f, new):
"""update 'parentfield' in tables"""
if f['fieldtype']=='Table':
if f['fieldtype'] in frappe.model.table_fields:
frappe.db.begin()
frappe.db.sql("""update `tab%s` set parentfield=%s where parentfield=%s""" \
% (f['options'], '%s', '%s'), (new, f['fieldname']))


+ 3
- 3
frappe/model/document.py 查看文件

@@ -12,7 +12,7 @@ from frappe.model.naming import set_new_name
from six import iteritems, string_types
from werkzeug.exceptions import NotFound, Forbidden
import hashlib, json
from frappe.model import optional_fields
from frappe.model import optional_fields, table_fields
from frappe.model.workflow import validate_workflow
from frappe.utils.global_search import update_global_search
from frappe.integrations.doctype.webhook import run_webhooks
@@ -489,7 +489,7 @@ class Document(BaseDocument):
value = self.get(field.fieldname)
original_value = self._doc_before_save.get(field.fieldname)

if field.fieldtype=='Table':
if field.fieldtype in table_fields:
fail = not self.is_child_table_same(field.fieldname)
elif field.fieldtype in ('Date', 'Datetime', 'Time'):
fail = str(value) != str(original_value)
@@ -756,7 +756,7 @@ class Document(BaseDocument):
def get_all_children(self, parenttype=None):
"""Returns all children documents from **Table** type field in a list."""
ret = []
for df in self.meta.get("fields", {"fieldtype": "Table"}):
for df in self.meta.get("fields", {"fieldtype": ['in', table_fields]}):
if parenttype:
if df.options==parenttype:
return self.get(df.fieldname)


+ 3
- 3
frappe/model/mapper.py 查看文件

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe, json
from frappe import _
from frappe.utils import cstr
from frappe.model import default_fields
from frappe.model import default_fields, table_fields
from six import string_types

@frappe.whitelist()
@@ -129,8 +129,8 @@ def map_doc(source_doc, target_doc, table_map, source_parent=None):
table_map["postprocess"](source_doc, target_doc, source_parent)

def map_fields(source_doc, target_doc, table_map, source_parent):
no_copy_fields = set([d.fieldname for d in source_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype=="Table")]
+ [d.fieldname for d in target_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype=="Table")]
no_copy_fields = set([d.fieldname for d in source_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype in table_fields)]
+ [d.fieldname for d in target_doc.meta.get("fields") if (d.no_copy==1 or d.fieldtype in table_fields)]
+ list(default_fields)
+ list(table_map.get("field_no_map", [])))



+ 3
- 3
frappe/model/meta.py 查看文件

@@ -20,7 +20,7 @@ from datetime import datetime
from six.moves import range
import frappe, json, os
from frappe.utils import cstr, cint
from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes
from frappe.model import default_fields, no_value_fields, optional_fields, data_fieldtypes, table_fields
from frappe.model.document import Document
from frappe.model.base_document import BaseDocument
from frappe.modules import load_doctype_module
@@ -150,7 +150,7 @@ class Meta(Document):
def get_table_fields(self):
if not hasattr(self, "_table_fields"):
if self.name!="DocType":
self._table_fields = self.get('fields', {"fieldtype":"Table"})
self._table_fields = self.get('fields', {"fieldtype": ['in', table_fields]})
else:
self._table_fields = doctype_table_fields

@@ -451,7 +451,7 @@ def is_single(doctype):
raise Exception('Cannot determine whether %s is single' % doctype)

def get_parent_dt(dt):
parent_dt = frappe.db.get_all('DocField', 'parent', dict(fieldtype='Table', options=dt), limit=1)
parent_dt = frappe.db.get_all('DocField', 'parent', dict(fieldtype=['in', frappe.model.table_fields], options=dt), limit=1)
return parent_dt and parent_dt[0].parent or ''

def set_fieldname(field_id, fieldname):


+ 20
- 10
frappe/model/rename_doc.py 查看文件

@@ -173,9 +173,11 @@ def validate_rename(doctype, new, meta, merge, force, ignore_permissions):
return new

def rename_doctype(doctype, old, new, force=False):
# change options for fieldtype Table
update_options_for_fieldtype("Table", old, new)
update_options_for_fieldtype("Link", old, new)
# change options for fieldtype Table, Table MultiSelect and Link
fields_with_options = ("Link",) + frappe.model.table_fields

for fieldtype in fields_with_options:
update_options_for_fieldtype(fieldtype, old, new)

# change options where select options are hardcoded i.e. listed
select_fields = get_select_fields(old, new)
@@ -352,13 +354,21 @@ def update_select_field_values(old, new):
.format(frappe.db.escape('%' + '\n' + old + '%'), frappe.db.escape('%' + old + '\n' + '%')), (old, new, new))

def update_parenttype_values(old, new):
child_doctypes = frappe.db.sql("""\
select options, fieldname from `tabDocField`
where parent=%s and fieldtype='Table'""", (new,), as_dict=1)

custom_child_doctypes = frappe.db.sql("""\
select options, fieldname from `tabCustom Field`
where dt=%s and fieldtype='Table'""", (new,), as_dict=1)
child_doctypes = frappe.db.get_all('DocField',
fields=['options', 'fieldname'],
filters={
'parent': new,
'fieldtype': ['in', frappe.model.table_fields]
}
)

custom_child_doctypes = frappe.db.get_all('Custom Field',
fields=['options', 'fieldname'],
filters={
'dt': new,
'fieldtype': ['in', frappe.model.table_fields]
}
)

child_doctypes += custom_child_doctypes
fields = [d['fieldname'] for d in child_doctypes]


+ 2
- 2
frappe/model/utils/rename_field.py 查看文件

@@ -5,7 +5,7 @@ from __future__ import unicode_literals, print_function

import frappe
import json
from frappe.model import no_value_fields
from frappe.model import no_value_fields, table_fields
from frappe.utils.password import rename_password_field
from frappe.model.utils.user_settings import update_user_settings_data, sync_user_settings

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

if new_field.fieldtype == "Table":
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"),


+ 2
- 1
frappe/public/build.json 查看文件

@@ -85,7 +85,8 @@
"public/js/frappe/form/controls/barcode.js",
"public/js/frappe/form/controls/geolocation.js",
"public/js/frappe/form/controls/multiselect.js",
"public/js/frappe/form/controls/multicheck.js"
"public/js/frappe/form/controls/multicheck.js",
"public/js/frappe/form/controls/table_multiselect.js"
],
"js/dialog.min.js": [
"public/js/frappe/dom.js",


+ 130
- 0
frappe/public/js/frappe/form/controls/table_multiselect.js 查看文件

@@ -0,0 +1,130 @@
frappe.ui.form.ControlTableMultiSelect = frappe.ui.form.ControlLink.extend({
make_input() {
this._super();

this.$input_area.addClass('form-control table-multiselect');
this.$input.removeClass('form-control');

this.$input.on("awesomplete-selectcomplete", () => {
this.$input.val('');
});

// used as an internal model to store values
this.rows = [];

this.$input_area.on('click', '.btn-remove', (e) => {
const $target = $(e.currentTarget);
const $value = $target.closest('.tb-selected-value');

const value = decodeURIComponent($value.data().value);
const link_field = this.get_link_field();
this.rows = this.rows.filter(row => row[link_field.fieldname] !== value);

this.parse_validate_and_set_in_model('');
});
this.$input_area.on('click', '.btn-link-to-form', (e) => {
const $target = $(e.currentTarget);
const $value = $target.closest('.tb-selected-value');

const value = decodeURIComponent($value.data().value);
const link_field = this.get_link_field();
frappe.set_route('Form', link_field.options, value);
});
},
setup_buttons() {
this.$input_area.find('.link-btn').remove();
},
parse(value) {
const link_field = this.get_link_field();

if (value) {
if (this.frm) {
const new_row = frappe.model.add_child(this.frm.doc, this.df.options, this.df.fieldname);
new_row[link_field.fieldname] = value;
} else {
this.rows.push({
[link_field.fieldname]: value
});
}
}

return this.rows;
},
validate(value) {
const rows = (value || []).slice();

// validate the value just entered
if (this.df.ignore_link_validation) {
return rows;
}

const link_field = this.get_link_field();
if (rows.length === 0) {
return rows;
}

const all_rows_except_last = rows.slice(0, rows.length - 1);
const last_row = rows[rows.length - 1];

// validate the last value entered
const link_value = last_row[link_field.fieldname];

// falsy value
if (!link_value) {
return all_rows_except_last;
}

// duplicate value
if (all_rows_except_last.map(row => row[link_field.fieldname]).includes(link_value)) {
return all_rows_except_last;
}

const validate_promise = this.validate_link_and_fetch(this.df, this.get_options(),
this.docname, link_value);

return validate_promise.then(validated_value => {
if (validated_value === link_value) {
return rows;
} else {
rows.pop();
return rows;
}
});
},
set_formatted_input(value) {
this.rows = value || [];
const link_field = this.get_link_field();
const values = this.rows.map(row => row[link_field.fieldname]);
this.set_pill_html(values);
},
set_pill_html(values) {
const html = values
.map(value => this.get_pill_html(value))
.join('');

this.$input_area.find('.tb-selected-value').remove();
this.$input_area.prepend(html);
},
get_pill_html(value) {
const encoded_value = encodeURIComponent(value);
return `<div class="btn-group tb-selected-value" data-value="${encoded_value}">
<button class="btn btn-default btn-xs btn-link-to-form">${__(value)}</button>
<button class="btn btn-default btn-xs btn-remove">
<i class="fa fa-remove text-muted"></i>
</button>
</div>`;
},
get_options() {
return (this.get_link_field() || {}).options;
},
get_link_field() {
if (!this._link_field) {
const meta = frappe.get_meta(this.df.options);
this._link_field = meta.fields.find(df => df.fieldtype === 'Link');
if (!this._link_field) {
throw new Error('Table MultiSelect requires a Table with atleast one Link field');
}
}
return this._link_field;
},
});

+ 10
- 0
frappe/public/js/frappe/form/formatters.js 查看文件

@@ -238,6 +238,16 @@ frappe.form.formatters = {
value = flt(flt(value) / 1024, 1) + "K";
}
return value;
},
TableMultiSelect: function(rows, df, options) {
rows = rows || [];
const meta = frappe.get_meta(df.options);
const link_field = meta.fields.find(df => df.fieldtype === 'Link');
const formatted_values = rows.map(row => {
const value = row[link_field.fieldname];
return frappe.format(value, link_field, options, row);
});
return formatted_values.join(', ');
}
}



+ 1
- 1
frappe/public/js/frappe/form/script_manager.js 查看文件

@@ -179,7 +179,7 @@ frappe.ui.form.ScriptManager = Class.extend({
// setup add fetch
$.each(this.frm.fields, function(i, field) {
setup_add_fetch(field.df);
if(field.df.fieldtype==="Table") {
if(frappe.model.table_fields.includes(field.df.fieldtype)) {
$.each(frappe.meta.get_docfields(field.df.options, me.frm.docname), function(i, df) {
setup_add_fetch(df);
});


+ 1
- 1
frappe/public/js/frappe/model/create_new.js 查看文件

@@ -266,7 +266,7 @@ $.extend(frappe.model, {
&& !(df && (!from_amend && cint(df.no_copy) == 1))) {

var value = doc[key] || [];
if (df.fieldtype === "Table") {
if (frappe.model.table_fields.includes(df.fieldtype)) {
for (var i = 0, j = value.length; i < j; i++) {
var d = value[i];
frappe.model.copy_doc(d, from_amend, newdoc, df.fieldname);


+ 3
- 3
frappe/public/js/frappe/model/meta.js 查看文件

@@ -138,7 +138,7 @@ $.extend(frappe.meta, {

get_table_fields: function(dt) {
return $.map(frappe.meta.docfield_list[dt], function(d) {
return d.fieldtype==='Table' ? d : null});
return frappe.model.table_fields.includes(d.fieldtype) ? d : null});
},

get_doctype_for_field: function(doctype, key) {
@@ -168,8 +168,8 @@ $.extend(frappe.meta, {
},

get_parentfield: function(parent_dt, child_dt) {
var df = (frappe.get_doc("DocType", parent_dt).fields || []).filter(function(d)
{ return d.fieldtype==="Table" && d.options===child_dt })
var df = (frappe.get_doc("DocType", parent_dt).fields || [])
.filter(df => frappe.model.table_fields.includes(df.fieldtype) && df.options===child_dt)
if(!df.length)
throw "parentfield not found for " + parent_dt + ", " + child_dt;
return df[0].fieldname;


+ 8
- 4
frappe/public/js/frappe/model/model.js 查看文件

@@ -4,7 +4,7 @@
frappe.provide('frappe.model');

$.extend(frappe.model, {
no_value_type: ['Section Break', 'Column Break', 'HTML', 'Table',
no_value_type: ['Section Break', 'Column Break', 'HTML', 'Table', 'Table MultiSelect',
'Button', 'Image', 'Fold', 'Heading'],

layout_fields: ['Section Break', 'Column Break', 'Fold'],
@@ -33,6 +33,8 @@ $.extend(frappe.model, {
{fieldname:'parent', fieldtype:'Data', label:__('Parent')},
],

table_fields: ['Table', 'Table MultiSelect'],

new_names: {},
events: {},
user_settings: {},
@@ -96,10 +98,12 @@ $.extend(frappe.model, {
if(locals.DocType[doctype]) {
callback && callback();
} else {
var cached_timestamp = null;
let cached_timestamp = null;
let cached_doc = null;

if(localStorage["_doctype:" + doctype]) {
let cached_docs = JSON.parse(localStorage["_doctype:" + doctype]);
let cached_doc = cached_docs.filter(doc => doc.name === doctype)[0];
cached_doc = cached_docs.filter(doc => doc.name === doctype)[0];
if(cached_doc) {
cached_timestamp = cached_doc.modified;
}
@@ -304,7 +308,7 @@ $.extend(frappe.model, {
var val = locals[dt] && locals[dt][dn] && locals[dt][dn][fn];
var df = frappe.meta.get_docfield(dt, fn, dn);

if(df.fieldtype=='Table') {
if(frappe.model.table_fields.includes(df.fieldtype)) {
var ret = false;
$.each(locals[df.options] || {}, function(k,d) {
if(d.parent==dn && d.parenttype==dt && d.parentfield==df.fieldname) {


+ 5
- 5
frappe/public/js/frappe/model/sync.js 查看文件

@@ -106,10 +106,10 @@ $.extend(frappe.model, {
if (source[key] == undefined) delete target[key];
});
}
for (let fieldname in doc) {
let df = frappe.meta.get_field(doc.doctype, fieldname);
if (df && df.fieldtype === 'Table') {
if (df && frappe.model.table_fields.includes(df.fieldtype)) {
// table
if (!(doc[fieldname] instanceof Array)) {
doc[fieldname] = [];
@@ -118,7 +118,7 @@ $.extend(frappe.model, {
if (!(local_doc[fieldname] instanceof Array)) {
local_doc[fieldname] = [];
}
// child table, override each row and append new rows if required
for (let i=0; i < doc[fieldname].length; i++ ) {
let d = doc[fieldname][i];
@@ -144,7 +144,7 @@ $.extend(frappe.model, {
// row exists, just copy the values
Object.assign(local_d, d);
clear_keys(d, local_d);
} else {
local_doc[fieldname].push(d);
if (!d.parent) d.parent = doc.name;
@@ -170,7 +170,7 @@ $.extend(frappe.model, {
local_doc[fieldname] = doc[fieldname];
}
}
// clear keys on parent
clear_keys(doc, local_doc);
}


+ 1
- 1
frappe/public/js/frappe/ui/filters/field_select.js 查看文件

@@ -138,7 +138,7 @@ frappe.ui.FieldSelect = Class.extend({
if(me.doctype && df.parent==me.doctype) {
label = __(df.label);
table = me.doctype;
if(df.fieldtype=='Table') me.table_fields.push(df);
if(frappe.model.table_fields.includes(df.fieldtype)) me.table_fields.push(df);
} else {
label = __(df.label) + ' (' + __(df.parent) + ')';
table = df.parent;


+ 1
- 1
frappe/public/js/frappe/ui/filters/filters.js 查看文件

@@ -658,7 +658,7 @@ frappe.ui.FieldSelect = Class.extend({
if(me.doctype && df.parent==me.doctype) {
var label = __(df.label);
var table = me.doctype;
if(df.fieldtype=='Table') me.table_fields.push(df);
if(frappe.model.table_fields.includes(df.fieldtype)) me.table_fields.push(df);
} else {
var label = __(df.label) + ' (' + __(df.parent) + ')';
var table = df.parent;


+ 1
- 1
frappe/public/js/frappe/views/kanban/kanban_view.js 查看文件

@@ -149,7 +149,7 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
// quick entry
var mandatory = meta.fields.filter((df) => df.reqd && !doc[df.fieldname]);

if (mandatory.some(df => df.fieldtype === 'Table') || mandatory.length > 1) {
if (mandatory.some(df => frappe.model.table_fields.includes(df.fieldtype)) || mandatory.length > 1) {
quick_entry = true;
}



+ 1
- 1
frappe/public/js/legacy/client_script_helpers.js 查看文件

@@ -279,7 +279,7 @@ _f.Frm.prototype.set_value = function(field, value, if_missing) {
var fieldobj = me.fields_dict[f];
if(fieldobj) {
if(!if_missing || !frappe.model.has_value(me.doctype, me.doc.name, f)) {
if(fieldobj.df.fieldtype==="Table" && $.isArray(v)) {
if(frappe.model.table_fields.includes(fieldobj.df.fieldtype) && $.isArray(v)) {

frappe.model.clear_table(me.doc, fieldobj.df.fieldname);



+ 3
- 1
frappe/public/js/legacy/form.js 查看文件

@@ -213,7 +213,9 @@ _f.Frm.prototype.watch_model_updates = function() {
});

// on table fields
var table_fields = frappe.get_children("DocType", me.doctype, "fields", {fieldtype:"Table"});
var table_fields = frappe.get_children("DocType", me.doctype, "fields", {
fieldtype: ["in", frappe.model.table_fields]
});

// using $.each to preserve df via closure
$.each(table_fields, function(i, df) {


+ 23
- 0
frappe/public/less/controls.less 查看文件

@@ -54,3 +54,26 @@
.markdown-toggle, .html-toggle {
margin-bottom: 5px;
}

/* table multiselect */
.table-multiselect {
display: flex;
align-items: center;
flex-wrap: wrap;
height: auto;
padding: 10px;
padding-bottom: 5px;
}

.table-multiselect.form-control input {
outline: none;
border: none;
padding: 0;
font-size: @text-medium;
}

.tb-selected-value {
display: inline-block;
margin-right: 5px;
margin-bottom: 5px;
}

+ 6
- 0
frappe/utils/formatters.py 查看文件

@@ -81,4 +81,10 @@ def format_value(value, df=None, doc=None, currency=None, translated=False):
elif df.get("fieldtype") == "Markdown Editor":
return frappe.utils.markdown(value)

elif df.get("fieldtype") == "Table MultiSelect":
meta = frappe.get_meta(df.options)
link_field = [df for df in meta.fields if df.fieldtype == 'Link'][0]
values = [v.get(link_field.fieldname, 'asdf') for v in value]
return ', '.join(values)

return value

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

@@ -75,7 +75,7 @@ def rebuild_for_doctype(doctype):
meta = frappe.get_meta(doctype)
if cint(meta.istable) == 1:
parent_doctypes = frappe.get_all("DocField", fields="parent", filters={
"fieldtype": "Table",
"fieldtype": ["in", frappe.model.table_fields],
"options": doctype
})
for p in parent_doctypes:
@@ -229,7 +229,7 @@ def update_global_search(doc):

content = []
for field in doc.meta.get_global_search_fields():
if doc.get(field.fieldname) and field.fieldtype != "Table":
if doc.get(field.fieldname) and field.fieldtype not in frappe.model.table_fields:
content.append(get_formatted_value(doc.get(field.fieldname), field))

tags = (doc.get('_user_tags') or '').strip()


Loading…
取消
儲存