@@ -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 | ||||
} | } |
@@ -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 |
@@ -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: | ||||
@@ -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"}) | ||||
@@ -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() |
@@ -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)] | ||||
@@ -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", | ||||
@@ -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)); | |||||
} | } | ||||
}); | }); |
@@ -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_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 | ||||
@@ -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 }) | ||||
@@ -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) { | ||||
@@ -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]''' | ||||