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:
parent
ad9cd8be8e
commit
c65bc6f229
|
@ -7,43 +7,53 @@
|
||||||
${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.tagit.css'))}
|
${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.tagit.css'))}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
% if initial_recipient_names is not Undefined:
|
var recipient_mappings = new Map([
|
||||||
var initial_recipients = {
|
<% last = len(available_recipients) %>
|
||||||
% for i, (uuid, name) in enumerate(initial_recipient_names.iteritems(), 1):
|
% for i, (uuid, entry) in enumerate(sorted(available_recipients.iteritems(), key=lambda r: r[1]), 1):
|
||||||
'${uuid}': "${name.replace('"', '\\""')|n}"${',' if i < len(initial_recipient_names) else ''}
|
['${uuid}', ${json.dumps(entry)|n}]${',' if i < last else ''}
|
||||||
% endfor
|
% endfor
|
||||||
};
|
]);
|
||||||
% endif
|
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
var recipients = $('.recipients input');
|
var recipients = $('.recipients input');
|
||||||
|
|
||||||
recipients.tagit({
|
recipients.tagit({
|
||||||
|
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
delay: 0,
|
delay: 0,
|
||||||
minLength: 2,
|
minLength: 2,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
|
removeConfirmation: true,
|
||||||
|
|
||||||
source: function(request, response) {
|
source: function(request, response) {
|
||||||
$.get('${url('messages.recipients')}', {term: request.term}, response);
|
var term = request.term.toLowerCase();
|
||||||
},
|
var data = [];
|
||||||
select: function(event, ui) {
|
recipient_mappings.forEach(function(name, uuid) {
|
||||||
recipients.tagit('createTag', ui.item.value + ',' + ui.item.label);
|
if (!name.toLowerCase && name.name) {
|
||||||
// Preventing the tag input to be updated with the chosen value.
|
name = name.name;
|
||||||
return false;
|
}
|
||||||
|
if (name.toLowerCase().indexOf(term) >= 0) {
|
||||||
|
data.push({value: uuid, label: name});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
response(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeTagAdded: function(event, ui) {
|
|
||||||
var label = ui.tagLabel.split(',');
|
beforeTagAdded: ${self.before_tag_added()},
|
||||||
var value = label.shift();
|
|
||||||
$(ui.tag).find('.tagit-hidden-field').val(value);
|
beforeTagRemoved: function(event, ui) {
|
||||||
if (label.length) {
|
|
||||||
$(ui.tag).find('.tagit-label').text(label.join());
|
// Unfortunately we're responsible for cleaning up the hidden
|
||||||
} else {
|
// field, since the values there do not match the tag labels.
|
||||||
$(ui.tag).find('.tagit-label').text(initial_recipients[value]);
|
var tags = recipients.tagit('assignedTags');
|
||||||
}
|
var uuid = ui.tag.data('uuid');
|
||||||
},
|
tags = tags.filter(function(element) {
|
||||||
removeConfirmation: true
|
return element != uuid;
|
||||||
|
});
|
||||||
|
recipients.data('ui-tagit')._updateSingleTagsField(tags);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// set focus to recipients field
|
// set focus to recipients field
|
||||||
|
@ -61,6 +71,18 @@
|
||||||
</style>
|
</style>
|
||||||
</%def>
|
</%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()">
|
<%def name="context_menu_items()">
|
||||||
% if request.has_perm('messages.list'):
|
% if request.has_perm('messages.list'):
|
||||||
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>
|
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>
|
||||||
|
|
|
@ -63,6 +63,9 @@ class AutocompleteView(View):
|
||||||
"""
|
"""
|
||||||
return instance.uuid
|
return instance.uuid
|
||||||
|
|
||||||
|
def get_data(self, term):
|
||||||
|
return self.query(term).all()
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""
|
"""
|
||||||
View implementation.
|
View implementation.
|
||||||
|
@ -73,5 +76,5 @@ class AutocompleteView(View):
|
||||||
term = self.prepare_term(term)
|
term = self.prepare_term(term)
|
||||||
if not term:
|
if not term:
|
||||||
return []
|
return []
|
||||||
results = self.query(term).all()
|
results = self.get_data(term)
|
||||||
return [{'label': self.display(x), 'value': self.value(x)} for x in results]
|
return [{'label': self.display(x), 'value': self.value(x)} for x in results]
|
||||||
|
|
|
@ -26,6 +26,7 @@ Message Views
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import json
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from rattail import enum
|
from rattail import enum
|
||||||
|
@ -39,7 +40,7 @@ from webhelpers.html import tags, HTML
|
||||||
|
|
||||||
from tailbone import forms
|
from tailbone import forms
|
||||||
from tailbone.db import Session
|
from tailbone.db import Session
|
||||||
from tailbone.views import MasterView, AutocompleteView
|
from tailbone.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
class SubjectFieldRenderer(formalchemy.FieldRenderer):
|
class SubjectFieldRenderer(formalchemy.FieldRenderer):
|
||||||
|
@ -80,7 +81,7 @@ class RecipientsFieldRenderer(formalchemy.FieldRenderer):
|
||||||
def render(self, **kwargs):
|
def render(self, **kwargs):
|
||||||
uuids = self.value
|
uuids = self.value
|
||||||
value = ','.join(uuids) if uuids else ''
|
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):
|
def deserialize(self):
|
||||||
value = self.params.getone(self.name).split(',')
|
value = self.params.getone(self.name).split(',')
|
||||||
|
@ -150,8 +151,8 @@ class MessagesView(MasterView):
|
||||||
|
|
||||||
def query(self, session):
|
def query(self, session):
|
||||||
return session.query(model.Message)\
|
return session.query(model.Message)\
|
||||||
.outerjoin(model.MessageRecipient)\
|
.outerjoin(model.MessageRecipient)\
|
||||||
.filter(model.MessageRecipient.recipient == self.request.user)
|
.filter(model.MessageRecipient.recipient == self.request.user)
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
|
|
||||||
|
@ -206,9 +207,9 @@ class MessagesView(MasterView):
|
||||||
message.subject = "Re: {}".format(old_message.subject)
|
message.subject = "Re: {}".format(old_message.subject)
|
||||||
message.body = self.get_reply_body(old_message)
|
message.body = self.get_reply_body(old_message)
|
||||||
if self.replying == 'all':
|
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
|
for r in old_message.recipients
|
||||||
if r.recipient.active]
|
if self.filter_reply_recipient(r.recipient)]
|
||||||
value = dict(value)
|
value = dict(value)
|
||||||
value.pop(self.request.user.uuid, None)
|
value.pop(self.request.user.uuid, None)
|
||||||
value = sorted(value.iteritems(), key=lambda r: r[1])
|
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:
|
if old_message.sender is not self.request.user and old_message.sender.active:
|
||||||
value.insert(0, old_message.sender_uuid)
|
value.insert(0, old_message.sender_uuid)
|
||||||
fs.recipients.set(value=value)
|
fs.recipients.set(value=value)
|
||||||
else:
|
elif self.filter_reply_recipient(old_message.sender):
|
||||||
fs.recipients.set(value=[old_message.sender_uuid])
|
fs.recipients.set(value=[old_message.sender.uuid])
|
||||||
fs.focus = fs.body
|
fs.focus = fs.body
|
||||||
|
|
||||||
elif self.viewing:
|
elif self.viewing:
|
||||||
|
@ -228,6 +229,9 @@ class MessagesView(MasterView):
|
||||||
fs.subject,
|
fs.subject,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def filter_reply_recipient(self, user):
|
||||||
|
return user.active
|
||||||
|
|
||||||
def get_reply_header(self, message):
|
def get_reply_header(self, message):
|
||||||
sent = pytz.utc.localize(message.sent)
|
sent = pytz.utc.localize(message.sent)
|
||||||
sent = localtime(self.rattail_config, sent)
|
sent = localtime(self.rattail_config, sent)
|
||||||
|
@ -247,22 +251,34 @@ class MessagesView(MasterView):
|
||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
def get_recipient(self, message):
|
def get_recipient(self, message):
|
||||||
for recip in message.recipients:
|
"""
|
||||||
if recip.recipient is self.request.user:
|
Fetch the recipient from the given message, which corresponds to the
|
||||||
return recip
|
current (request) user.
|
||||||
|
"""
|
||||||
|
for recipient in message.recipients:
|
||||||
|
if recipient.recipient is self.request.user:
|
||||||
|
return recipient
|
||||||
|
|
||||||
def template_kwargs_create(self, **kwargs):
|
def template_kwargs_create(self, **kwargs):
|
||||||
|
kwargs['available_recipients'] = self.get_available_recipients()
|
||||||
|
kwargs['json'] = json
|
||||||
if self.replying:
|
if self.replying:
|
||||||
message = self.get_instance()
|
kwargs['original_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
|
|
||||||
return kwargs
|
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):
|
def template_kwargs_view(self, **kwargs):
|
||||||
message = kwargs['instance']
|
message = kwargs['instance']
|
||||||
return {'message': message,
|
return {'message': message,
|
||||||
|
@ -395,29 +411,13 @@ class SentView(MessagesView):
|
||||||
|
|
||||||
def query(self, session):
|
def query(self, session):
|
||||||
return session.query(model.Message)\
|
return session.query(model.Message)\
|
||||||
.filter(model.Message.sender == self.request.user)
|
.filter(model.Message.sender == self.request.user)
|
||||||
|
|
||||||
def configure_grid(self, g):
|
def configure_grid(self, g):
|
||||||
super(SentView, self).configure_grid(g)
|
super(SentView, self).configure_grid(g)
|
||||||
g.filters['sender'].default_active = False
|
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):
|
def includeme(config):
|
||||||
|
|
||||||
config.add_tailbone_permission('messages', 'messages.list', "List/Search Messages")
|
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',
|
config.add_view(SentView, attr='index', route_name='messages.sent',
|
||||||
permission='messages.list')
|
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)
|
MessagesView.defaults(config)
|
||||||
|
|
Loading…
Reference in a new issue