浏览代码

[enhancement] [wip] links menu in forms

version-14
Rushabh Mehta 9 年前
父节点
当前提交
daa9c094d0
共有 19 个文件被更改,包括 297 次插入85 次删除
  1. +2
    -2
      frappe/desk/doctype/todo/todo.json
  2. +39
    -1
      frappe/desk/notifications.py
  3. +33
    -8
      frappe/model/db_query.py
  4. +18
    -0
      frappe/model/meta.py
  5. +1
    -1
      frappe/model/utils/__init__.py
  6. +7
    -6
      frappe/modules/__init__.py
  7. +7
    -6
      frappe/public/build.json
  8. +100
    -40
      frappe/public/js/frappe/form/dashboard.js
  9. +19
    -0
      frappe/public/js/frappe/form/templates/form_documents.html
  10. +0
    -0
      frappe/public/js/frappe/form/templates/form_sidebar.html
  11. +0
    -0
      frappe/public/js/frappe/form/templates/grid_body.html
  12. +0
    -0
      frappe/public/js/frappe/form/templates/grid_form.html
  13. +0
    -0
      frappe/public/js/frappe/form/templates/print_layout.html
  14. +0
    -0
      frappe/public/js/frappe/form/templates/set_sharing.html
  15. +0
    -0
      frappe/public/js/frappe/form/templates/users_in_sidebar.html
  16. +41
    -9
      frappe/public/js/frappe/list/doclistview.js
  17. +5
    -0
      frappe/public/js/frappe/model/meta.js
  18. +5
    -3
      frappe/public/js/legacy/form.js
  19. +20
    -9
      frappe/utils/data.py

+ 2
- 2
frappe/desk/doctype/todo/todo.json 查看文件

@@ -403,7 +403,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2016-04-07 01:35:53.935024",
"modified": "2016-04-12 02:50:32.042427",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Desk", "module": "Desk",
"name": "ToDo", "name": "ToDo",
@@ -453,7 +453,7 @@
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"search_fields": "description, reference_type, reference_name", "search_fields": "description, reference_type, reference_name",
"sort_order": "ASC",
"sort_order": "DESC",
"title_field": "description", "title_field": "description",
"track_seen": 1 "track_seen": 1
} }

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

@@ -2,9 +2,11 @@
# MIT License. See license.txt # MIT License. See license.txt


from __future__ import unicode_literals from __future__ import unicode_literals

import frappe import frappe
from frappe.utils import time_diff_in_seconds, now, now_datetime, DATETIME_FORMAT from frappe.utils import time_diff_in_seconds, now, now_datetime, DATETIME_FORMAT
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from frappe import _


@frappe.whitelist() @frappe.whitelist()
def get_notifications(): def get_notifications():
@@ -165,4 +167,40 @@ def get_notification_config():
def get_filters_for(doctype): def get_filters_for(doctype):
'''get open filters for doctype''' '''get open filters for doctype'''
config = get_notification_config() config = get_notification_config()
return config.get('for_doctype').get(doctype, {})
return config.get('for_doctype').get(doctype, {})

@frappe.whitelist()
def get_open_count(doctype, name):
'''Get open count for given transactions and filters

:param doctype: Reference DocType
:param name: Reference Name
:param transactions: List of transactions (json/dict)
:param filters: optional filters (json/list)'''

doc = frappe.get_doc(doctype, name)
if not doc.has_permission('read'):
frappe.msgprint(_("Not permitted"), raise_exception=True)

links = frappe.get_meta(doctype).get_links_setup()

# compile all items in a list
items = []
for group in links.transactions:
items.extend(group.get('items'))

out = []
for doctype in items:
filters = get_filters_for(doctype)
if filters:
# get the fieldname for the current document
# we only need open documents related to the current document
fieldname = links.get('non_standard_fieldnames', {}).get(doctype, links.fieldname)
filters[fieldname] = name

if filters:
open_count = len(frappe.get_list(doctype, fields='name',
filters=filters, limit_page_length=6, distinct=True))
out.append({'name': doctype, 'count': open_count})

return out

+ 33
- 8
frappe/model/db_query.py 查看文件

@@ -25,7 +25,8 @@ class DatabaseQuery(object):
def execute(self, query=None, fields=None, filters=None, or_filters=None, def execute(self, query=None, fields=None, filters=None, or_filters=None,
docstatus=None, group_by=None, order_by=None, limit_start=False, docstatus=None, group_by=None, order_by=None, limit_start=False,
limit_page_length=None, as_list=False, with_childnames=False, debug=False, limit_page_length=None, as_list=False, with_childnames=False, debug=False,
ignore_permissions=False, user=None, with_comment_count=False):
ignore_permissions=False, user=None, with_comment_count=False,
join='left join', distinct=False):
if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user): if not ignore_permissions and not frappe.has_permission(self.doctype, "read", user=user):
raise frappe.PermissionError, self.doctype raise frappe.PermissionError, self.doctype


@@ -56,6 +57,8 @@ class DatabaseQuery(object):
self.limit_page_length = cint(limit_page_length) if limit_page_length else None self.limit_page_length = cint(limit_page_length) if limit_page_length else None
self.with_childnames = with_childnames self.with_childnames = with_childnames
self.debug = debug self.debug = debug
self.join = join
self.distinct = distinct
self.as_list = as_list self.as_list = as_list
self.flags.ignore_permissions = ignore_permissions self.flags.ignore_permissions = ignore_permissions
self.user = user or frappe.session.user self.user = user or frappe.session.user
@@ -77,6 +80,9 @@ class DatabaseQuery(object):
if args.conditions: if args.conditions:
args.conditions = "where " + args.conditions args.conditions = "where " + args.conditions


if self.distinct:
args.fields = 'distinct ' + args.fields

query = """select %(fields)s from %(tables)s %(conditions)s query = """select %(fields)s from %(tables)s %(conditions)s
%(group_by)s %(order_by)s %(limit)s""" % args %(group_by)s %(order_by)s %(limit)s""" % args


@@ -99,8 +105,9 @@ class DatabaseQuery(object):
args.tables = self.tables[0] args.tables = self.tables[0]


# left join parent, child tables # left join parent, child tables
for tname in self.tables[1:]:
args.tables += " left join " + tname + " on " + tname + '.parent = ' + self.tables[0] + '.name'
for child in self.tables[1:]:
args.tables += " {join} {child} on ({child}.parent = {main}.name)".format(join=self.join,
child=child, main=self.tables[0])


if self.grouped_or_conditions: if self.grouped_or_conditions:
self.conditions.append("({0})".format(" or ".join(self.grouped_or_conditions))) self.conditions.append("({0})".format(" or ".join(self.grouped_or_conditions)))
@@ -111,6 +118,8 @@ class DatabaseQuery(object):
args.conditions += (' or ' if args.conditions else "") + \ args.conditions += (' or ' if args.conditions else "") + \
' or '.join(self.or_conditions) ' or '.join(self.or_conditions)


self.set_field_tables()

args.fields = ', '.join(self.fields) args.fields = ', '.join(self.fields)


self.set_order_by(args) self.set_order_by(args)
@@ -170,6 +179,14 @@ class DatabaseQuery(object):
if (not self.flags.ignore_permissions) and (not frappe.has_permission(doctype)): if (not self.flags.ignore_permissions) and (not frappe.has_permission(doctype)):
raise frappe.PermissionError, doctype raise frappe.PermissionError, doctype


def set_field_tables(self):
'''If there are more than one table, the fieldname must not be ambigous.
If the fieldname is not explicitly mentioned, set the default table'''
if len(self.tables) > 1:
for i, f in enumerate(self.fields):
if '.' not in f:
self.fields[i] = '{0}.{1}'.format(self.tables[0], f)

def set_optional_columns(self): def set_optional_columns(self):
"""Removes optional columns like `_user_tags`, `_comments` etc. if not in table""" """Removes optional columns like `_user_tags`, `_comments` etc. if not in table"""
columns = frappe.db.get_table_columns(self.doctype) columns = frappe.db.get_table_columns(self.doctype)
@@ -401,11 +418,19 @@ class DatabaseQuery(object):


if not group_function_without_group_by: if not group_function_without_group_by:
sort_field = sort_order = None sort_field = sort_order = None
if meta.sort_field:
sort_field = meta.sort_field
sort_order = meta.sort_order

args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, sort_field or "modified", sort_order or "desc")
if meta.sort_field and ',' in meta.sort_field:
# multiple sort given in doctype definition
# Example:
# `idx desc, modified desc`
# will covert to
# `tabItem`.`idx` desc, `tabItem`.`modified` desc
args.order_by = ', '.join(['`tab{0}`.`{1}` {2}'.format(self.doctype,
f.split()[0].strip(), f.split()[1].strip()) for f in meta.sort_field.split(',')])
else:
sort_field = meta.sort_field or 'modified'
sort_order = meta.sort_order or 'desc'

args.order_by = "`tab{0}`.`{1}` {2}".format(self.doctype, sort_field or "modified", sort_order or "desc")


# draft docs always on top # draft docs always on top
if meta.is_submittable: if meta.is_submittable:


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

@@ -10,6 +10,7 @@ from frappe.model import integer_docfield_properties, default_fields, no_value_f
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.base_document import BaseDocument from frappe.model.base_document import BaseDocument
from frappe.model.db_schema import type_map from frappe.model.db_schema import type_map
from frappe.modules import load_doctype_module


def get_meta(doctype, cached=True): def get_meta(doctype, cached=True):
if cached: if cached:
@@ -92,12 +93,17 @@ class Meta(Document):
return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname) return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname)


def get_field(self, fieldname): def get_field(self, fieldname):
'''Return docfield from meta'''
if not self._fields: if not self._fields:
for f in self.get("fields"): for f in self.get("fields"):
self._fields[f.fieldname] = f self._fields[f.fieldname] = f


return self._fields.get(fieldname) return self._fields.get(fieldname)


def has_field(self, fieldname):
'''Returns True if fieldname exists'''
return True if self.get_field(fieldname) else False

def get_label(self, fieldname): def get_label(self, fieldname):
return self.get_field(fieldname).label return self.get_field(fieldname).label


@@ -245,6 +251,18 @@ class Meta(Document):


return self.high_permlevel_fields return self.high_permlevel_fields


def get_links_setup(self):
'''Returns setup for documents related to this doctype.

This method will return the `links_setup` property in the
`[doctype]_links.py` file in the doctype folder'''
try:
module = load_doctype_module(self.name, suffix='_links')
return frappe._dict(module.links)
except ImportError:
return frappe._dict()


doctype_table_fields = [ doctype_table_fields = [
frappe._dict({"fieldname": "fields", "options": "DocField"}), frappe._dict({"fieldname": "fields", "options": "DocField"}),
frappe._dict({"fieldname": "permissions", "options": "DocPerm"}) frappe._dict({"fieldname": "permissions", "options": "DocPerm"})


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

@@ -26,4 +26,4 @@ def set_field_property(filters, key, value):
d.save() d.save()
print 'Updated {0}'.format(d.name) print 'Updated {0}'.format(d.name)


frappe.db.commit()
frappe.db.commit()

+ 7
- 6
frappe/modules/__init__.py 查看文件

@@ -51,26 +51,27 @@ def get_doctype_module(doctype):
return frappe.cache().get_value("doctype_modules", make_modules_dict)[doctype] return frappe.cache().get_value("doctype_modules", make_modules_dict)[doctype]


doctype_python_modules = {} doctype_python_modules = {}
def load_doctype_module(doctype, module=None, prefix=""):
def load_doctype_module(doctype, module=None, prefix="", suffix=""):
"""Returns the module object for given doctype.""" """Returns the module object for given doctype."""
if not module: if not module:
module = get_doctype_module(doctype) module = get_doctype_module(doctype)


app = get_module_app(module) app = get_module_app(module)


key = (app, doctype, prefix)
key = (app, doctype, prefix, suffix)


if key not in doctype_python_modules: if key not in doctype_python_modules:
doctype_python_modules[key] = frappe.get_module(get_module_name(doctype, module, prefix))
doctype_python_modules[key] = frappe.get_module(get_module_name(doctype, module, prefix, suffix))


return doctype_python_modules[key] return doctype_python_modules[key]


def get_module_name(doctype, module, prefix="", app=None):
return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}'.format(\
def get_module_name(doctype, module, prefix="", suffix="", app=None):
return '{app}.{module}.doctype.{doctype}.{prefix}{doctype}{suffix}'.format(\
app = scrub(app or get_module_app(module)), app = scrub(app or get_module_app(module)),
module = scrub(module), module = scrub(module),
doctype = scrub(doctype), doctype = scrub(doctype),
prefix=prefix)
prefix=prefix,
suffix=suffix)


def get_module_app(module): def get_module_app(module):
return frappe.local.module_app[scrub(module)] return frappe.local.module_app[scrub(module)]


+ 7
- 6
frappe/public/build.json 查看文件

@@ -144,6 +144,13 @@
"public/css/form_grid.css" "public/css/form_grid.css"
], ],
"js/form.min.js": [ "js/form.min.js": [
"public/js/frappe/form/templates/grid_form.html",
"public/js/frappe/form/templates/grid_body.html",
"public/js/frappe/form/templates/print_layout.html",
"public/js/frappe/form/templates/users_in_sidebar.html",
"public/js/frappe/form/templates/set_sharing.html",
"public/js/frappe/form/templates/form_sidebar.html",
"public/js/frappe/form/templates/form_documents.html",
"public/js/frappe/views/formview.js", "public/js/frappe/views/formview.js",
"public/js/legacy/form.js", "public/js/legacy/form.js",
"public/js/legacy/clientscriptAPI.js", "public/js/legacy/clientscriptAPI.js",
@@ -151,20 +158,14 @@
"public/js/frappe/form/dashboard.js", "public/js/frappe/form/dashboard.js",
"public/js/frappe/form/save.js", "public/js/frappe/form/save.js",
"public/js/frappe/form/script_manager.js", "public/js/frappe/form/script_manager.js",
"public/js/frappe/form/grid_form.html",
"public/js/frappe/form/grid_body.html",
"public/js/frappe/form/grid.js", "public/js/frappe/form/grid.js",
"public/js/frappe/form/linked_with.js", "public/js/frappe/form/linked_with.js",
"public/js/frappe/form/workflow.js", "public/js/frappe/form/workflow.js",
"public/js/frappe/form/print_layout.html",
"public/js/frappe/form/print.js", "public/js/frappe/form/print.js",
"public/js/frappe/form/sidebar.js", "public/js/frappe/form/sidebar.js",
"public/js/frappe/form/user_image.js", "public/js/frappe/form/user_image.js",
"public/js/frappe/form/users_in_sidebar.html",
"public/js/frappe/form/share.js", "public/js/frappe/form/share.js",
"public/js/frappe/form/set_sharing.html",
"public/js/frappe/form/form_viewers.js", "public/js/frappe/form/form_viewers.js",
"public/js/frappe/form/form_sidebar.html",


"public/js/frappe/form/footer/form_footer.html", "public/js/frappe/form/footer/form_footer.html",
"public/js/frappe/form/footer/timeline.html", "public/js/frappe/form/footer/timeline.html",


+ 100
- 40
frappe/public/js/frappe/form/dashboard.js 查看文件

@@ -4,7 +4,7 @@
frappe.ui.form.Dashboard = Class.extend({ frappe.ui.form.Dashboard = Class.extend({
init: function(opts) { init: function(opts) {
$.extend(this, opts); $.extend(this, opts);
this.wrapper = $('<div class="form-dashboard shaded-section" style="padding-top: 10px;"></div>')
this.wrapper = $('<div class="form-dashboard shaded-section"></div>')
.prependTo(this.frm.layout.wrapper); .prependTo(this.frm.layout.wrapper);
this.body = $('<div class="row"></div>').appendTo(this.wrapper) this.body = $('<div class="row"></div>').appendTo(this.wrapper)
.css("padding", "15px 30px"); .css("padding", "15px 30px");
@@ -13,55 +13,34 @@ frappe.ui.form.Dashboard = Class.extend({
reset: function() { reset: function() {
this.wrapper.toggle(false); this.wrapper.toggle(false);
this.body.empty(); this.body.empty();
this.headline = null;
this.badge_area = $('<div class="hidden" \
style="padding-left: 15px; padding-right: 15px;"></div>').appendTo(this.body);
this.clear_headline();
}, },
set_headline: function(html) { set_headline: function(html) {
if(!this.headline) if(!this.headline)
this.headline = this.headline =
$('<h4 class="form-headline col-md-12"></h4>').prependTo(this.body);
this.headline.html(html);
$('<h4 class="form-headline col-md-12 hidden"></h4>').prependTo(this.body);
this.headline.html(html).removeClass('hidden');
this.wrapper.toggle(true); this.wrapper.toggle(true);
}, },
set_headline_alert: function(text, alert_class, icon) {
if(!alert_class) alert_class = "alert-warning";
this.set_headline(repl('<div class="alert %(alert_class)s">%(icon)s%(text)s</div>', {
"alert_class": alert_class || "",
"icon": icon ? '<i class="'+icon+'" /> ' : "",
"text": text
}));
clear_headline: function() {
if(this.headline) {
this.headline.empty().addClass('hidden');
}
}, },
add_doctype_badge: function(doctype, fieldname) {
if(frappe.model.can_read(doctype)) {
this.add_badge(__(doctype), doctype, function(show_open) {
frappe.route_options = {};
frappe.route_options[fieldname] = cur_frm.doc.name;
if(show_open) {
$.extend(frappe.route_options, frappe.ui.notifications.get_filters(doctype));
}
frappe.set_route("List", doctype);
}).attr("data-doctype", doctype);
set_headline_alert: function(text, alert_class) {
if(text) {
if(!alert_class) alert_class = "alert-warning";
this.set_headline(repl('<div class="alert %(alert_class)s">%(text)s</div>', {
"alert_class": alert_class || "",
"text": text
}));
} else {
this.clear_headline();
} }
}, },
add_badge: function(label, doctype, onclick) {
var badge = $(repl('<div class="col-xs-6">\
<div style="margin-bottom: 10px; height: 22px;"><a data-doctype=%(doctype)s\
class="badge-link small">%(label)s</a>\
<span class="open-notification hidden" data-doctype="%(doctype)s"></span>\
</div></div>', {label:label, doctype:doctype}))
.appendTo(this.body)

badge.find(".badge-link").click(onclick);
badge.find('.open-notification').on('click', function() { onclick(true); })
this.wrapper.toggle(true);


return badge.find(".alert-badge");
},
set_badge_count: function(doctype, count) {
$(this.wrapper)
.find('.open-notification[data-doctype="'+doctype+'"]')
.removeClass('hidden')
.html(cint(count));
},
add_progress: function(title, percent) { add_progress: function(title, percent) {
var progress_chart = this.make_progress_chart(title); var progress_chart = this.make_progress_chart(title);


@@ -103,5 +82,86 @@ frappe.ui.form.Dashboard = Class.extend({
.removeClass().addClass("progress-chart col-md-" + cols); .removeClass().addClass("progress-chart col-md-" + cols);


return progress_chart; return progress_chart;
},
show_documents: function() {
this.reset();
if(this.frm.doc.__islocal)
return;

this.links = this.frm.doc.__onload.links;

this.render_document_list();
this.set_open_count();

},
render_document_list: function() {
var me = this;
$(frappe.render_template('form_documents',
{transactions: this.links.transactions}))
.appendTo(this.badge_area)

// bind links
this.badge_area.find(".badge-link").on('click', function() {
me.open_document_list($(this).attr('data-doctype'));
});

// bind open notifications
this.badge_area.find('.open-notification').on('click', function() {
me.open_document_list($(this).attr('data-doctype'), true);
});

this.wrapper.toggle(true);
this.badge_area.removeClass('hidden');
},
open_document_list: function(doctype, show_open) {
// show document list with filters
frappe.route_options = this.get_document_filter();
if(show_open) {
$.extend(frappe.route_options, frappe.ui.notifications.get_filters(doctype));
}

frappe.set_route("List", doctype);
},
get_document_filter: function(doctype) {
// return the default filter for the given document
// like {"customer": frm.doc.name}
var filter = {};
var fieldname = this.links.non_standard_fieldnames
? (this.links.non_standard_fieldnames[doctype] || this.links.fieldname)
: this.links.fieldname;
filter[fieldname] = this.frm.doc.name;
return filter;
},
set_open_count: function() {
// list all items from the transaction list
var items = [],
me = this;

this.links.transactions.forEach(function(group) {
group.items.forEach(function(item) { items.push(item); });
});

frappe.call({
type: "GET",
method: "frappe.desk.notifications.get_open_count",
args: {
doctype: this.frm.doc.doctype,
name: this.frm.doc.name,
},
callback: function(r) {
$.each(r.message, function(i, d) {
if(d.count) {
me.frm.dashboard.set_badge_count(d.name, d.count > 5 ? '5+' : d.count)
}
})
}
});

},
set_badge_count: function(doctype, count) {
$(this.wrapper)
.find('.open-notification[data-doctype="'+doctype+'"]')
.removeClass('hidden')
.html(cint(count));
} }
}); });

+ 19
- 0
frappe/public/js/frappe/form/templates/form_documents.html 查看文件

@@ -0,0 +1,19 @@
<div class="form-documents">
{% for (var i=0; i < transactions.length; i++) { %}
{% if((i % 2)===0) { %}<div class="row">{% } %}
<div class="col-xs-6">
<h5 style="margin-top: 15px;">{{ transactions[i].label }}</h5>
{% for (var j=0; j < transactions[i].items.length; j++) {
var doctype = transactions[i].items[j]; %}
<div style="margin-bottom: 10px; height: 22px;">
<a data-doctype="{{ doctype }}" class="badge-link small">
{{ __(doctype) }}</a>
<span class="open-notification hidden" data-doctype="{{ doctype }}"></span>
</div>
{% } %}
</div>
{% if((i % 2)===1) { %}</div>{% } %}
{% } %}
<!-- finally, close one-column row -->
{% if((i % 2)===0) { %}</div>{% } %}
</div>

frappe/public/js/frappe/form/form_sidebar.html → frappe/public/js/frappe/form/templates/form_sidebar.html 查看文件


frappe/public/js/frappe/form/grid_body.html → frappe/public/js/frappe/form/templates/grid_body.html 查看文件


frappe/public/js/frappe/form/grid_form.html → frappe/public/js/frappe/form/templates/grid_form.html 查看文件


frappe/public/js/frappe/form/print_layout.html → frappe/public/js/frappe/form/templates/print_layout.html 查看文件


frappe/public/js/frappe/form/set_sharing.html → frappe/public/js/frappe/form/templates/set_sharing.html 查看文件


frappe/public/js/frappe/form/users_in_sidebar.html → frappe/public/js/frappe/form/templates/users_in_sidebar.html 查看文件


+ 41
- 9
frappe/public/js/frappe/list/doclistview.js 查看文件

@@ -275,10 +275,13 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
}, },


set_route_options: function() { set_route_options: function() {
// set filters from frappe.route_options
// before switching pages, frappe.route_options can have pre-set filters
// for the list view
var me = this; var me = this;
me.filter_list.clear_filters(); me.filter_list.clear_filters();
$.each(frappe.route_options, function(key, value) { $.each(frappe.route_options, function(key, value) {
var doctype = me.doctype;
var doctype = null;


// if `Child DocType.fieldname` // if `Child DocType.fieldname`
if (key.indexOf(".")!==-1) { if (key.indexOf(".")!==-1) {
@@ -286,10 +289,39 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
key = key.split(".")[1]; key = key.split(".")[1];
} }


if($.isArray(value)) {
me.filter_list.add_filter(doctype, key, value[0], value[1]);
} else {
me.filter_list.add_filter(doctype, key, "=", value);
// find the table in which the key exists
// for example the filter could be {"item_code": "X"}
// where item_code is in the child table.

// we can search all tables for mapping the doctype
if(!doctype) {
if(in_list(frappe.model.std_fields_list, key)) {
// standard
doctype = me.doctype;
} else if(frappe.meta.has_field(me.doctype, key)) {
// found in parent
doctype = me.doctype;
} else {
frappe.meta.get_table_fields(me.doctype).forEach(function(d) {
if(frappe.meta.has_field(d.options, key)) {
doctype = d.options;
return false;
}
});

if(!doctype) {
frappe.msgprint(__('Warning: Unable to find {0} in any table related to {1}', [
key, __(me.doctype)]));
}
}
}

if(doctype) {
if($.isArray(value)) {
me.filter_list.add_filter(doctype, key, value[0], value[1]);
} else {
me.filter_list.add_filter(doctype, key, "=", value);
}
} }
}); });
frappe.route_options = null; frappe.route_options = null;
@@ -489,11 +521,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
var no_print = false var no_print = false
docname = []; docname = [];
$.each(me.get_checked_items(), function(i, doc){ $.each(me.get_checked_items(), function(i, doc){
if(!is_submittable || doc.docstatus == 1 ||
(allow_print_for_cancelled && doc.docstatus == 2)||
(allow_print_for_draft && doc.docstatus == 0)||
if(!is_submittable || doc.docstatus == 1 ||
(allow_print_for_cancelled && doc.docstatus == 2)||
(allow_print_for_draft && doc.docstatus == 0)||
in_list(user_roles, "Administrator")) in_list(user_roles, "Administrator"))
docname.push(doc.name); docname.push(doc.name);
else else
no_print = true no_print = true


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

@@ -109,6 +109,11 @@ $.extend(frappe.meta, {
return frappe.meta.docfield_map[dt][fn]; return frappe.meta.docfield_map[dt][fn];
}, },


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

get_parentfield: function(parent_dt, child_dt) { get_parentfield: function(parent_dt, child_dt) {
var df = (frappe.get_doc("DocType", parent_dt).fields || []).filter(function(d) var df = (frappe.get_doc("DocType", parent_dt).fields || []).filter(function(d)
{ return d.fieldtype==="Table" && options===child_dt }) { return d.fieldtype==="Table" && options===child_dt })


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

@@ -578,8 +578,9 @@ _f.Frm.prototype.setnewdoc = function() {
}); });


// update seen // update seen
$('.list-id[data-name="'+ me.docname +'"]').addClass('seen');

if(this.meta.track_seen) {
$('.list-id[data-name="'+ me.docname +'"]').addClass('seen');
}
} }


_f.Frm.prototype.trigger_link_fields = function() { _f.Frm.prototype.trigger_link_fields = function() {
@@ -851,7 +852,8 @@ _f.Frm.prototype.get_perm = function(permlevel, access_type) {




_f.Frm.prototype.set_intro = function(txt, append) { _f.Frm.prototype.set_intro = function(txt, append) {
frappe.utils.set_intro(this, this.body, txt, append);
this.dashboard.set_headline_alert(txt);
//frappe.utils.set_intro(this, this.body, txt, append);
} }


_f.Frm.prototype.set_footnote = function(txt) { _f.Frm.prototype.set_footnote = function(txt) {


+ 20
- 9
frappe/utils/data.py 查看文件

@@ -647,6 +647,8 @@ def get_filter(doctype, f):
"value": "value":
} }
""" """
from frappe.model import default_fields, optional_fields

if isinstance(f, dict): if isinstance(f, dict):
key, value = f.items()[0] key, value = f.items()[0]
f = make_filter_tuple(doctype, key, value) f = make_filter_tuple(doctype, key, value)
@@ -660,20 +662,29 @@ def get_filter(doctype, f):
elif len(f) != 4: elif len(f) != 4:
frappe.throw("Filter must have 4 values (doctype, fieldname, operator, value): {0}".format(str(f))) frappe.throw("Filter must have 4 values (doctype, fieldname, operator, value): {0}".format(str(f)))


if not f[2]:
f = frappe._dict(doctype=f[0], fieldname=f[1], operator=f[2], value=f[3])

if not f.operator:
# if operator is missing # if operator is missing
f[2] = "="
f.operator = "="


valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in") valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in")
if f[2] not in valid_operators:
if f.operator not in valid_operators:
frappe.throw("Operator must be one of {0}".format(", ".join(valid_operators))) frappe.throw("Operator must be one of {0}".format(", ".join(valid_operators)))


return frappe._dict({
"doctype": f[0],
"fieldname": f[1],
"operator": f[2],
"value": f[3]
})

if f.doctype and (f.fieldname not in default_fields + optional_fields):
# verify fieldname belongs to the doctype
meta = frappe.get_meta(f.doctype)
if not meta.has_field(f.fieldname):

# try and match the doctype name from child tables
for df in meta.get_table_fields():
if frappe.get_meta(df.options).has_field(f.fieldname):
f.doctype = df.options
break

return f


def make_filter_tuple(doctype, key, value): def make_filter_tuple(doctype, key, value):
'''return a filter tuple like [doctype, key, operator, value]''' '''return a filter tuple like [doctype, key, operator, value]'''


正在加载...
取消
保存