Show Assigned To in list view

Paired between Anand and Rushabh
This commit is contained in:
Anand Doshi 2014-09-04 12:57:07 +05:30
parent 41aada3b5a
commit 023b5cbcdf
18 changed files with 196 additions and 45 deletions

View file

@ -6,6 +6,7 @@
import frappe
import frappe.defaults
import unittest
import json
test_records = frappe.get_test_records('Event')
@ -53,3 +54,43 @@ class TestEvent(unittest.TestCase):
# the name should be same!
self.assertEquals(ev.name, name)
def test_assign(self):
from frappe.widgets.form.assign_to import add
ev = frappe.get_doc(test_records[0]).insert()
add({
"assign_to": "test@example.com",
"doctype": "Event",
"name": ev.name,
"description": "Test Assignment"
})
ev = frappe.get_doc("Event", ev.name)
self.assertEquals(ev._assign, json.dumps(["test@example.com"]))
# add another one
add({
"assign_to": "test1@example.com",
"doctype": "Event",
"name": ev.name,
"description": "Test Assignment"
})
ev = frappe.get_doc("Event", ev.name)
self.assertEquals(ev._assign, json.dumps(["test@example.com", "test1@example.com"]))
# close an assignment
todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name,
"owner": "test1@example.com"})
todo.status = "Closed"
todo.save()
ev = frappe.get_doc("Event", ev.name)
self.assertEquals(ev._assign, json.dumps(["test@example.com"]))
# cleanup
ev.delete()

View file

@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe
import json
from frappe.model.document import Document
@ -15,11 +16,17 @@ class ToDo(Document):
if cur_status != self.status:
self.add_comment(frappe._("Assignment Status Changed"), "Assignment Completed")
def on_update(self):
self.update_in_reference()
def on_trash(self):
self.update_in_reference()
def add_comment(self, text, comment_type):
if not self.reference_type and self.reference_name:
return
comment = frappe.get_doc({
frappe.get_doc({
"doctype":"Comment",
"comment_by": frappe.session.user,
"comment_type": comment_type,
@ -32,6 +39,36 @@ class ToDo(Document):
description = self.description)
}).insert(ignore_permissions=True)
def update_in_reference(self):
if not (self.reference_type and self.reference_name):
return
try:
assignments = [d[0] for d in frappe.get_list("ToDo",
filters={
"reference_type": self.reference_type,
"reference_name": self.reference_name,
"status": "Open"
},
fields=["owner"], ignore_permissions=True, as_list=True)]
assignments.reverse()
frappe.db.set_value(self.reference_type, self.reference_name,
"_assign", json.dumps(assignments))
except Exception, e:
if e.args[0] == 1146 and frappe.flags.in_install:
# no table
return
elif e.args[0]==1054:
from frappe.model.db_schema import add_column
add_column(self.reference_type, "_assign", "Text")
self.update_in_reference()
else:
raise
# NOTE: todo is viewable if either owner or assigned_to or System Manager in roles
def get_permission_query_conditions(user):

View file

@ -134,7 +134,7 @@ class DatabaseQuery(object):
columns = frappe.db.get_table_columns(self.doctype)
to_remove = []
for fld in self.fields:
for f in ("_user_tags", "_comments"):
for f in ("_user_tags", "_comments", "_assign"):
if f in fld and not f in columns:
to_remove.append(fld)

View file

@ -413,6 +413,9 @@ class Document(BaseDocument):
self.docstatus = 2
self.save()
def delete(self):
frappe.delete_doc(self.doctype, self.name)
def run_before_save_methods(self):
if getattr(self, "ignore_validate", False):
return

View file

@ -51,3 +51,4 @@ frappe.patches.v4_1.file_manager_fix
frappe.patches.v4_2.print_with_letterhead
execute:frappe.delete_doc("DocType", "Control Panel", force=1)
frappe.patches.v4_2.refactor_website_routing
frappe.patches.v4_2.set_assign_in_doc

View file

@ -0,0 +1,6 @@
import frappe
def execute():
for name in frappe.db.sql_list("""select name from `tabToDo`
where ifnull(reference_type, '')!='' and ifnull(reference_name, '')!=''"""):
frappe.get_doc("ToDo", name).on_update()

View file

@ -71,6 +71,7 @@
"public/js/lib/microtemplate.js",
"public/html/print_template.html",
"public/html/list_info_template.html",
"public/js/legacy/globals.js",
"public/js/legacy/datatype.js",

View file

@ -25,3 +25,12 @@
width: 72px;
height: 72px;
}
.avatar-xs {
margin-right: 3px;
margin-top: -2px;
width: 15px;
height: 15px;
border: none;
border-radius: 3px;
}

View file

@ -89,7 +89,7 @@ div#freeze {
}
.list-row {
padding: 5px 15px;
padding: 5px 15px 10px;
margin: 0px -15px;
border-bottom: 1px solid #c7c7c7;
}
@ -177,7 +177,7 @@ div#freeze {
margin-top: 12px;
}
.doclist-row .filterable {
.filterable {
cursor: pointer;
}
@ -188,7 +188,7 @@ div#freeze {
.list-timestamp {
position: absolute;
right: 15px;
bottom: 2px;
bottom: 5px;
font-size: 70%;
color: #888;
}

View file

@ -0,0 +1,17 @@
{% if (tags.length) { %}
<span style="margin-right: 10px;" class="list-tag-preview">
{%= tags.join(", ") %}</span>
{% } %}
{% if (comments.length) { %}
<a style="margin-right: 10px;" href="#Form/{%= doctype %}/{%= encodeURIComponent(data.name) %}"
title="{%= comments[comments.length-1].comment %}">
<i class="icon-comments"></i> {%= comments.length %}
</a>
{% } %}
{% if (assign.length) { %}
{% for (var i=0, l=assign.length; i<l; i++) { %}
<span class="filterable" data-filter="_assign,like,%{%= assign[i] %}%">
{%= frappe.avatar(assign[i], "avatar-xs") %}</span>
{% }%}
{% } %}
{%= comment_when(data.modified) %}

View file

@ -101,6 +101,15 @@ frappe.form.formatters = {
});
return html;
},
Assign: function(value) {
var html = "";
$.each(JSON.parse(value || "[]"), function(i, v) {
if(v) html+= '<span class="label label-warning" \
style="margin-right: 7px;"\
data-field="_assign">'+v+'</span>';
});
return html;
},
SmallText: function(value) {
return frappe.form.formatters.Text(value);
},

View file

@ -12,16 +12,15 @@ frappe.user_info = function(uid) {
return frappe.boot.user_info[uid];
}
frappe.avatar = function(user, large, title) {
frappe.avatar = function(user, css_class, title) {
var image = frappe.utils.get_file_link(frappe.user_info(user).image);
var to_size = large ? 72 : 30;
if(!title) title = frappe.user_info(user).fullname;
return repl('<span class="avatar %(small_or_large)s" title="%(title)s">\
return repl('<span class="avatar %(css_class)s" title="%(title)s">\
<img src="%(image)s"></span>', {
image: image,
title: title,
small_or_large: large ? "avatar-large" : "avatar-small"
css_class: css_class || "avatar-small"
});
}
@ -61,9 +60,6 @@ $.extend(frappe.user, {
image: function(uid) {
return frappe.user_info(uid).image;
},
avatar: function(uid, large) {
return frappe.avatar(uid, large);
},
has_role: function(rl) {
if(typeof rl=='string')
rl = [rl];

View file

@ -10,7 +10,9 @@ $.extend(frappe.model, {
layout_fields: ['Section Break', 'Column Break', 'Fold'],
std_fields_list: ['name', 'owner', 'creation', 'modified', 'modified_by',
'_user_tags', '_comments', 'docstatus', 'parent', 'parenttype', 'parentfield', 'idx'],
'_user_tags', '_comments', '_assign', 'docstatus',
'parent', 'parenttype', 'parentfield', 'idx'],
std_fields: [
{fieldname:'name', fieldtype:'Link', label:__('ID')},
{fieldname:'owner', fieldtype:'Data', label:__('Created By')},
@ -20,6 +22,7 @@ $.extend(frappe.model, {
{fieldname:'modified_by', fieldtype:'Data', label:__('Last Updated By')},
{fieldname:'_user_tags', fieldtype:'Data', label:__('Tags')},
{fieldname:'_comments', fieldtype:'Text', label:__('Comments')},
{fieldname:'_assign', fieldtype:'Text', label:__('Assigned To')},
{fieldname:'docstatus', fieldtype:'Int', label:__('Document Status')},
],

View file

@ -361,7 +361,7 @@ frappe.ui.Listing = Class.extend({
if(fieldname=='_user_tags') {
// and for tags
this.filter_list.add_filter(doctype, fieldname,
'like', '%' + label);
'like', '%' + label + '%');
} else {
// or for rest using "in"
filter.set_values(doctype, fieldname, 'in', v + ', ' + label);
@ -370,9 +370,9 @@ frappe.ui.Listing = Class.extend({
} else {
// no filter for this item,
// setup one
if(['_user_tags', '_comments'].indexOf(fieldname)!==-1) {
if(['_user_tags', '_comments', '_assign'].indexOf(fieldname)!==-1) {
this.filter_list.add_filter(doctype, fieldname,
'like', '%' + label);
'like', '%' + label + '%');
} else {
this.filter_list.add_filter(doctype, fieldname, '=', label);
}

View file

@ -5,7 +5,7 @@ frappe.ui.TagEditor = Class.extend({
init: function(opts) {
/* docs:
Arguments
- parent
- user_tags
- doctype
@ -20,21 +20,38 @@ frappe.ui.TagEditor = Class.extend({
placeholderText: __('Add Tag'),
onTagAdded: function(ev, tag) {
if(me.initialized && !me.refreshing) {
var tag = tag.find('.tagit-label').text();
return frappe.call({
method: 'frappe.widgets.tags.add_tag',
args: me.get_args(tag.find('.tagit-label').text())
});
args: me.get_args(tag),
callback: function(r) {
var user_tags = me.user_tags.split(",");
user_tags.push(tag)
me.user_tags = user_tags.join(",");
me.on_change && me.on_change(me.user_tags);
}
});
}
},
onTagRemoved: function(ev, tag) {
if(!me.refreshing) {
var tag = tag.find('.tagit-label').text();
return frappe.call({
method: 'frappe.widgets.tags.remove_tag',
args: me.get_args(tag.find('.tagit-label').text())
args: me.get_args(tag),
callback: function(r) {
var user_tags = me.user_tags.split(",");
user_tags.splice(user_tags.indexOf(tag), 1);
me.user_tags = user_tags.join(",");
me.on_change && me.on_change(me.user_tags);
}
});
}
}
});
});
if (!this.user_tags) {
this.user_tags = "";
}
this.refresh(this.user_tags);
this.initialized = true;
},
@ -51,15 +68,15 @@ frappe.ui.TagEditor = Class.extend({
me.refreshing = true;
me.$tags.tagit("removeAll");
if(!user_tags && this.frm)
if(!user_tags && this.frm)
user_tags = frappe.model.get_value(this.frm.doctype, this.frm.docname, "_user_tags");
if(user_tags) {
$.each(user_tags.split(','), function(i, v) {
me.$tags.tagit("createTag", v);
});
}
me.refreshing = false;
}
})
})

View file

@ -44,7 +44,7 @@ frappe.views.ListView = Class.extend({
}
$.each(['name', 'owner', 'docstatus', '_user_tags', '_comments', 'modified',
'modified_by'], function(i, fieldname) { add_field(fieldname); })
'modified_by', '_assign'], function(i, fieldname) { add_field(fieldname); })
// add title field
if(this.meta.title_field) {
@ -221,6 +221,7 @@ frappe.views.ListView = Class.extend({
var comments = data._comments ? JSON.parse(data._comments) : [],
tags = $.map((data._user_tags || "").split(","),
function(v) { return v ? v : null; }),
assign = data._assign ? JSON.parse(data._assign) : [],
me = this;
if(me.title_field && data[me.title_field]!==data.name) {
@ -230,24 +231,18 @@ frappe.views.ListView = Class.extend({
.html('<a href="#Form/'+ data.doctype+'/'+data.name +'">#' + data.name + "</a>");
}
$(row).find(".list-timestamp").remove();
var timestamp_and_comment =
$('<div class="list-timestamp">')
.appendTo(row)
.html(""
+ (tags.length ? (
'<span style="margin-right: 10px;" class="list-tag-preview">' + tags.join(", ") + '</span>'
): "")
+ (comments.length ?
('<a style="margin-right: 10px;" href="#Form/'+
this.doctype + '/' + data.name
+'" title="'+
comments[comments.length-1].comment
+'"><i class="icon-comments"></i> '
+ comments.length + " " + (
comments.length===1 ? __("comment") : __("comments")) + '</a>')
: "")
+ comment_when(data.modified));
.html(frappe.render(frappe.templates.list_info_template, {
"tags": tags,
"comments": comments,
"assign": assign,
"data": data,
"doctype": this.doctype
}));
},
render_tags: function(row, data) {
@ -272,7 +267,11 @@ frappe.views.ListView = Class.extend({
doctype: this.doctype,
docname: data.name
},
user_tags: data._user_tags
user_tags: data._user_tags,
on_change: function(user_tags) {
data._user_tags = user_tags;
me.render_timestamp_and_comments(row, data);
}
});
tag_editor.$w.on("click", ".tagit-label", function() {
me.doclistview.set_filter("_user_tags",

View file

@ -220,8 +220,12 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
width: (docfield ? cint(docfield.width) : 120) || 120,
formatter: function(row, cell, value, columnDef, dataContext) {
var docfield = columnDef.docfield;
if(docfield.fieldname==="_user_tags") docfield.fieldtype = "Tag";
if(docfield.fieldname==="_comments") docfield.fieldtype = "Comment";
docfield.fieldtype = {
"_user_tags": "Tag",
"_comments": "Comment",
"_assign": "Assign"
}[docfield.fieldname] || docfield.fieldtype;
if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") {
docfield.link_onclick =
repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s").page.reportview.run()',

View file

@ -22,7 +22,15 @@ def get(args=None):
@frappe.whitelist()
def add(args=None):
"""add in someone's to do list"""
"""add in someone's to do list
args = {
"assign_to": ,
"doctype": ,
"name": ,
"description":
}
"""
if not args:
args = frappe.local.form_dict