Refactor magic recipients field when sending new message.

Uses local cache of user mappings instead of AJAX calls; has just enough
customization hooks to allow for a department/user mapping for MJ...
This commit is contained in:
Lance Edgar 2016-02-11 17:05:56 -06:00
parent ad9cd8be8e
commit c65bc6f229
3 changed files with 86 additions and 66 deletions

View file

@ -7,43 +7,53 @@
${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.tagit.css'))}
<script type="text/javascript">
% if initial_recipient_names is not Undefined:
var initial_recipients = {
% for i, (uuid, name) in enumerate(initial_recipient_names.iteritems(), 1):
'${uuid}': "${name.replace('"', '\\""')|n}"${',' if i < len(initial_recipient_names) else ''}
% endfor
};
% endif
var recipient_mappings = new Map([
<% last = len(available_recipients) %>
% for i, (uuid, entry) in enumerate(sorted(available_recipients.iteritems(), key=lambda r: r[1]), 1):
['${uuid}', ${json.dumps(entry)|n}]${',' if i < last else ''}
% endfor
]);
$(function() {
var recipients = $('.recipients input');
recipients.tagit({
autocomplete: {
delay: 0,
minLength: 2,
autoFocus: true,
removeConfirmation: true,
source: function(request, response) {
$.get('${url('messages.recipients')}', {term: request.term}, response);
},
select: function(event, ui) {
recipients.tagit('createTag', ui.item.value + ',' + ui.item.label);
// Preventing the tag input to be updated with the chosen value.
return false;
var term = request.term.toLowerCase();
var data = [];
recipient_mappings.forEach(function(name, uuid) {
if (!name.toLowerCase && name.name) {
name = name.name;
}
if (name.toLowerCase().indexOf(term) >= 0) {
data.push({value: uuid, label: name});
}
});
response(data);
}
},
beforeTagAdded: function(event, ui) {
var label = ui.tagLabel.split(',');
var value = label.shift();
$(ui.tag).find('.tagit-hidden-field').val(value);
if (label.length) {
$(ui.tag).find('.tagit-label').text(label.join());
} else {
$(ui.tag).find('.tagit-label').text(initial_recipients[value]);
}
},
removeConfirmation: true
beforeTagAdded: ${self.before_tag_added()},
beforeTagRemoved: function(event, ui) {
// Unfortunately we're responsible for cleaning up the hidden
// field, since the values there do not match the tag labels.
var tags = recipients.tagit('assignedTags');
var uuid = ui.tag.data('uuid');
tags = tags.filter(function(element) {
return element != uuid;
});
recipients.data('ui-tagit')._updateSingleTagsField(tags);
}
});
// set focus to recipients field
@ -61,6 +71,18 @@
</style>
</%def>
<%def name="before_tag_added()">
function(event, ui) {
// Lookup the name in cached mapping, and show that on the tag, instead
// of the UUID. The tagit widget should take care of keeping the
// hidden field in sync for us, still using the UUID.
var uuid = ui.tagLabel;
var name = recipient_mappings.get(uuid);
ui.tag.find('.tagit-label').html(name);
}
</%def>
<%def name="context_menu_items()">
% if request.has_perm('messages.list'):
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>

View file

@ -63,6 +63,9 @@ class AutocompleteView(View):
"""
return instance.uuid
def get_data(self, term):
return self.query(term).all()
def __call__(self):
"""
View implementation.
@ -73,5 +76,5 @@ class AutocompleteView(View):
term = self.prepare_term(term)
if not term:
return []
results = self.query(term).all()
results = self.get_data(term)
return [{'label': self.display(x), 'value': self.value(x)} for x in results]

View file

@ -26,6 +26,7 @@ Message Views
from __future__ import unicode_literals, absolute_import
import json
import pytz
from rattail import enum
@ -39,7 +40,7 @@ from webhelpers.html import tags, HTML
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView
class SubjectFieldRenderer(formalchemy.FieldRenderer):
@ -80,7 +81,7 @@ class RecipientsFieldRenderer(formalchemy.FieldRenderer):
def render(self, **kwargs):
uuids = self.value
value = ','.join(uuids) if uuids else ''
return text_field(self.name, value=value, maxlength=self.length, **kwargs)
return text_field(self.name, value=value, **kwargs)
def deserialize(self):
value = self.params.getone(self.name).split(',')
@ -150,8 +151,8 @@ class MessagesView(MasterView):
def query(self, session):
return session.query(model.Message)\
.outerjoin(model.MessageRecipient)\
.filter(model.MessageRecipient.recipient == self.request.user)
.outerjoin(model.MessageRecipient)\
.filter(model.MessageRecipient.recipient == self.request.user)
def configure_grid(self, g):
@ -206,9 +207,9 @@ class MessagesView(MasterView):
message.subject = "Re: {}".format(old_message.subject)
message.body = self.get_reply_body(old_message)
if self.replying == 'all':
value = [(r.recipient_uuid, r.recipient.person.display_name)
value = [(r.recipient.uuid, r.recipient.person.display_name)
for r in old_message.recipients
if r.recipient.active]
if self.filter_reply_recipient(r.recipient)]
value = dict(value)
value.pop(self.request.user.uuid, None)
value = sorted(value.iteritems(), key=lambda r: r[1])
@ -216,8 +217,8 @@ class MessagesView(MasterView):
if old_message.sender is not self.request.user and old_message.sender.active:
value.insert(0, old_message.sender_uuid)
fs.recipients.set(value=value)
else:
fs.recipients.set(value=[old_message.sender_uuid])
elif self.filter_reply_recipient(old_message.sender):
fs.recipients.set(value=[old_message.sender.uuid])
fs.focus = fs.body
elif self.viewing:
@ -228,6 +229,9 @@ class MessagesView(MasterView):
fs.subject,
])
def filter_reply_recipient(self, user):
return user.active
def get_reply_header(self, message):
sent = pytz.utc.localize(message.sent)
sent = localtime(self.rattail_config, sent)
@ -247,22 +251,34 @@ class MessagesView(MasterView):
return '\n'.join(lines)
def get_recipient(self, message):
for recip in message.recipients:
if recip.recipient is self.request.user:
return recip
"""
Fetch the recipient from the given message, which corresponds to the
current (request) user.
"""
for recipient in message.recipients:
if recipient.recipient is self.request.user:
return recipient
def template_kwargs_create(self, **kwargs):
kwargs['available_recipients'] = self.get_available_recipients()
kwargs['json'] = json
if self.replying:
message = self.get_instance()
kwargs['original_message'] = message
names = {message.sender_uuid: message.sender.person.display_name}
if self.replying == 'all':
for recip in message.recipients:
if recip.recipient is not message.sender:
names[recip.recipient_uuid] = recip.recipient.person.display_name
kwargs['initial_recipient_names'] = names
kwargs['original_message'] = self.get_instance()
return kwargs
def get_available_recipients(self):
"""
Return the full mapping of recipients which may be included in a
message sent by the current user.
"""
recips = {}
users = Session.query(model.User)\
.join(model.Person)\
.filter(model.User.active == True)
for user in users:
recips[user.uuid] = user.person.display_name
return recips
def template_kwargs_view(self, **kwargs):
message = kwargs['instance']
return {'message': message,
@ -395,29 +411,13 @@ class SentView(MessagesView):
def query(self, session):
return session.query(model.Message)\
.filter(model.Message.sender == self.request.user)
.filter(model.Message.sender == self.request.user)
def configure_grid(self, g):
super(SentView, self).configure_grid(g)
g.filters['sender'].default_active = False
class RecipientsAutocomplete(AutocompleteView):
"""
Autocomplete AJAX view for the recipients field when sending a new message.
"""
def query(self, term):
return Session.query(model.User)\
.join(model.Person)\
.filter(model.User.active == True)\
.filter(model.Person.display_name.ilike('%{}%'.format(term)))\
.order_by(model.Person.display_name)
def display(self, user):
return user.person.display_name
def includeme(config):
config.add_tailbone_permission('messages', 'messages.list', "List/Search Messages")
@ -437,9 +437,4 @@ def includeme(config):
config.add_view(SentView, attr='index', route_name='messages.sent',
permission='messages.list')
# recipients autocomplete
config.add_route('messages.recipients', '/messages/recipients')
config.add_view(RecipientsAutocomplete, route_name='messages.recipients',
renderer='json', permission='messages.create')
MessagesView.defaults(config)