@@ -403,7 +403,7 @@ | |||
"issingle": 0, | |||
"istable": 0, | |||
"max_attachments": 0, | |||
"modified": "2016-04-07 01:35:53.935024", | |||
"modified": "2016-04-12 02:50:32.042427", | |||
"modified_by": "Administrator", | |||
"module": "Desk", | |||
"name": "ToDo", | |||
@@ -453,7 +453,7 @@ | |||
"read_only": 0, | |||
"read_only_onload": 0, | |||
"search_fields": "description, reference_type, reference_name", | |||
"sort_order": "ASC", | |||
"sort_order": "DESC", | |||
"title_field": "description", | |||
"track_seen": 1 | |||
} |
@@ -2,9 +2,11 @@ | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import frappe | |||
from frappe.utils import time_diff_in_seconds, now, now_datetime, DATETIME_FORMAT | |||
from dateutil.relativedelta import relativedelta | |||
from frappe import _ | |||
@frappe.whitelist() | |||
def get_notifications(): | |||
@@ -165,4 +167,40 @@ def get_notification_config(): | |||
def get_filters_for(doctype): | |||
'''get open filters for doctype''' | |||
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 |
@@ -25,7 +25,8 @@ class DatabaseQuery(object): | |||
def execute(self, query=None, fields=None, filters=None, or_filters=None, | |||
docstatus=None, group_by=None, order_by=None, limit_start=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): | |||
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.with_childnames = with_childnames | |||
self.debug = debug | |||
self.join = join | |||
self.distinct = distinct | |||
self.as_list = as_list | |||
self.flags.ignore_permissions = ignore_permissions | |||
self.user = user or frappe.session.user | |||
@@ -77,6 +80,9 @@ class DatabaseQuery(object): | |||
if args.conditions: | |||
args.conditions = "where " + args.conditions | |||
if self.distinct: | |||
args.fields = 'distinct ' + args.fields | |||
query = """select %(fields)s from %(tables)s %(conditions)s | |||
%(group_by)s %(order_by)s %(limit)s""" % args | |||
@@ -99,8 +105,9 @@ class DatabaseQuery(object): | |||
args.tables = self.tables[0] | |||
# 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: | |||
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 "") + \ | |||
' or '.join(self.or_conditions) | |||
self.set_field_tables() | |||
args.fields = ', '.join(self.fields) | |||
self.set_order_by(args) | |||
@@ -170,6 +179,14 @@ class DatabaseQuery(object): | |||
if (not self.flags.ignore_permissions) and (not frappe.has_permission(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): | |||
"""Removes optional columns like `_user_tags`, `_comments` etc. if not in table""" | |||
columns = frappe.db.get_table_columns(self.doctype) | |||
@@ -401,11 +418,19 @@ class DatabaseQuery(object): | |||
if not group_function_without_group_by: | |||
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 | |||
if meta.is_submittable: | |||
@@ -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.base_document import BaseDocument | |||
from frappe.model.db_schema import type_map | |||
from frappe.modules import load_doctype_module | |||
def get_meta(doctype, cached=True): | |||
if cached: | |||
@@ -92,12 +93,17 @@ class Meta(Document): | |||
return { "fields": "DocField", "permissions": "DocPerm"}.get(fieldname) | |||
def get_field(self, fieldname): | |||
'''Return docfield from meta''' | |||
if not self._fields: | |||
for f in self.get("fields"): | |||
self._fields[f.fieldname] = f | |||
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): | |||
return self.get_field(fieldname).label | |||
@@ -245,6 +251,18 @@ class Meta(Document): | |||
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 = [ | |||
frappe._dict({"fieldname": "fields", "options": "DocField"}), | |||
frappe._dict({"fieldname": "permissions", "options": "DocPerm"}) | |||
@@ -26,4 +26,4 @@ def set_field_property(filters, key, value): | |||
d.save() | |||
print 'Updated {0}'.format(d.name) | |||
frappe.db.commit() | |||
frappe.db.commit() |
@@ -51,26 +51,27 @@ def get_doctype_module(doctype): | |||
return frappe.cache().get_value("doctype_modules", make_modules_dict)[doctype] | |||
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.""" | |||
if not module: | |||
module = get_doctype_module(doctype) | |||
app = get_module_app(module) | |||
key = (app, doctype, prefix) | |||
key = (app, doctype, prefix, suffix) | |||
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] | |||
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)), | |||
module = scrub(module), | |||
doctype = scrub(doctype), | |||
prefix=prefix) | |||
prefix=prefix, | |||
suffix=suffix) | |||
def get_module_app(module): | |||
return frappe.local.module_app[scrub(module)] | |||
@@ -144,6 +144,13 @@ | |||
"public/css/form_grid.css" | |||
], | |||
"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/legacy/form.js", | |||
"public/js/legacy/clientscriptAPI.js", | |||
@@ -151,20 +158,14 @@ | |||
"public/js/frappe/form/dashboard.js", | |||
"public/js/frappe/form/save.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/linked_with.js", | |||
"public/js/frappe/form/workflow.js", | |||
"public/js/frappe/form/print_layout.html", | |||
"public/js/frappe/form/print.js", | |||
"public/js/frappe/form/sidebar.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/set_sharing.html", | |||
"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/timeline.html", | |||
@@ -4,7 +4,7 @@ | |||
frappe.ui.form.Dashboard = Class.extend({ | |||
init: function(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); | |||
this.body = $('<div class="row"></div>').appendTo(this.wrapper) | |||
.css("padding", "15px 30px"); | |||
@@ -13,55 +13,34 @@ frappe.ui.form.Dashboard = Class.extend({ | |||
reset: function() { | |||
this.wrapper.toggle(false); | |||
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) { | |||
if(!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); | |||
}, | |||
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) { | |||
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); | |||
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)); | |||
} | |||
}); |
@@ -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> |
@@ -275,10 +275,13 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
}, | |||
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; | |||
me.filter_list.clear_filters(); | |||
$.each(frappe.route_options, function(key, value) { | |||
var doctype = me.doctype; | |||
var doctype = null; | |||
// if `Child DocType.fieldname` | |||
if (key.indexOf(".")!==-1) { | |||
@@ -286,10 +289,39 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
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; | |||
@@ -489,11 +521,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({ | |||
var no_print = false | |||
docname = []; | |||
$.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")) | |||
docname.push(doc.name); | |||
else | |||
no_print = true | |||
@@ -109,6 +109,11 @@ $.extend(frappe.meta, { | |||
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) { | |||
var df = (frappe.get_doc("DocType", parent_dt).fields || []).filter(function(d) | |||
{ return d.fieldtype==="Table" && options===child_dt }) | |||
@@ -578,8 +578,9 @@ _f.Frm.prototype.setnewdoc = function() { | |||
}); | |||
// 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() { | |||
@@ -851,7 +852,8 @@ _f.Frm.prototype.get_perm = function(permlevel, access_type) { | |||
_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) { | |||
@@ -647,6 +647,8 @@ def get_filter(doctype, f): | |||
"value": | |||
} | |||
""" | |||
from frappe.model import default_fields, optional_fields | |||
if isinstance(f, dict): | |||
key, value = f.items()[0] | |||
f = make_filter_tuple(doctype, key, value) | |||
@@ -660,20 +662,29 @@ def get_filter(doctype, f): | |||
elif len(f) != 4: | |||
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 | |||
f[2] = "=" | |||
f.operator = "=" | |||
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))) | |||
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): | |||
'''return a filter tuple like [doctype, key, operator, value]''' | |||