Add initial support for 'messages' views.
This commit is contained in:
parent
c2a6b0dd50
commit
84ebb158bc
27
tailbone/templates/messages/archive/index.mako
Normal file
27
tailbone/templates/messages/archive/index.mako
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/messages/index.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Message Archive</%def>
|
||||||
|
|
||||||
|
<%def name="head_tags()">
|
||||||
|
${parent.head_tags()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
destination = "Inbox";
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
${parent.context_menu_items()}
|
||||||
|
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Sent Messages", url('messages.sent'))}</li>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="grid_tools()">
|
||||||
|
${h.form(url('messages.move_bulk'), name='move-selected')}
|
||||||
|
${h.hidden('destination', value='inbox')}
|
||||||
|
${h.hidden('uuids')}
|
||||||
|
<button type="submit">Move 0 selected to Inbox</button>
|
||||||
|
${h.end_form()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
27
tailbone/templates/messages/inbox/index.mako
Normal file
27
tailbone/templates/messages/inbox/index.mako
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/messages/index.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Message Inbox</%def>
|
||||||
|
|
||||||
|
<%def name="head_tags()">
|
||||||
|
${parent.head_tags()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
destination = "Archive";
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
${parent.context_menu_items()}
|
||||||
|
<li>${h.link_to("Go to my Message Archive", url('messages.archive'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Sent Messages", url('messages.sent'))}</li>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="grid_tools()">
|
||||||
|
${h.form(url('messages.move_bulk'), name='move-selected')}
|
||||||
|
${h.hidden('destination', value='archive')}
|
||||||
|
${h.hidden('uuids')}
|
||||||
|
<button type="submit">Move 0 selected to Archive</button>
|
||||||
|
${h.end_form()}
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
58
tailbone/templates/messages/index.mako
Normal file
58
tailbone/templates/messages/index.mako
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="head_tags()">
|
||||||
|
${parent.head_tags()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
var destination = null;
|
||||||
|
|
||||||
|
function update_move_button() {
|
||||||
|
var count = $('.newgrid tbody td.checkbox input:checked').length;
|
||||||
|
$('form[name="move-selected"] button')
|
||||||
|
.button('option', 'label', "Move " + count + " selected to " + destination)
|
||||||
|
.button('option', 'disabled', count < 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
update_move_button();
|
||||||
|
|
||||||
|
$('.newgrid-wrapper').on('click', 'thead th.checkbox input', function() {
|
||||||
|
update_move_button();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.newgrid-wrapper').on('click', 'tbody td.checkbox input', function() {
|
||||||
|
update_move_button();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('form[name="move-selected"]').submit(function() {
|
||||||
|
var uuids = [];
|
||||||
|
$('.newgrid tbody td.checkbox input:checked').each(function() {
|
||||||
|
uuids.push($(this).parents('tr:first').data('uuid'));
|
||||||
|
});
|
||||||
|
if (! uuids.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$(this).find('[name="uuids"]').val(uuids.toString());
|
||||||
|
$(this).find('button')
|
||||||
|
.button('option', 'label', "Moving " + uuids.length + " messages to " + destination + "...")
|
||||||
|
.button('disable');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
.newgrid table {
|
||||||
|
position: relative;
|
||||||
|
top: -32px;
|
||||||
|
}
|
||||||
|
.newgrid .pager {
|
||||||
|
position: relative;
|
||||||
|
top: -32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
12
tailbone/templates/messages/sent/index.mako
Normal file
12
tailbone/templates/messages/sent/index.mako
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/master/index.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">Sent Messages</%def>
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
${parent.context_menu_items()}
|
||||||
|
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Message Archive", url('messages.archive'))}</li>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
31
tailbone/templates/messages/view.mako
Normal file
31
tailbone/templates/messages/view.mako
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/master/view.mako" />
|
||||||
|
|
||||||
|
<%def name="context_menu_items()">
|
||||||
|
% if recipient:
|
||||||
|
% if recipient.status == rattail.enum.MESSAGE_STATUS_INBOX:
|
||||||
|
<li>${h.link_to("Back to Message Inbox", url('messages.inbox'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Message Archive", url('messages.archive'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Sent Messages", url('messages.sent'))}</li>
|
||||||
|
<li>${h.link_to("Move this Message to my Archive", url('messages.move', uuid=instance.uuid) + '?dest=archive')}</li>
|
||||||
|
% else:
|
||||||
|
<li>${h.link_to("Back to Message Archive", url('messages.archive'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Sent Messages", url('messages.sent'))}</li>
|
||||||
|
<li>${h.link_to("Move this Message to my Inbox", url('messages.move', uuid=instance.uuid) + '?dest=inbox')}</li>
|
||||||
|
% endif
|
||||||
|
% else:
|
||||||
|
<li>${h.link_to("Back to Sent Messages", url('messages.sent'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Message Inbox", url('messages.inbox'))}</li>
|
||||||
|
<li>${h.link_to("Go to my Message Archive", url('messages.archive'))}</li>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
292
tailbone/views/messages.py
Normal file
292
tailbone/views/messages.py
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2016 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Message Views
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
from rattail import enum
|
||||||
|
from rattail.db import model
|
||||||
|
|
||||||
|
import formalchemy
|
||||||
|
from pyramid import httpexceptions
|
||||||
|
from webhelpers.html import tags
|
||||||
|
|
||||||
|
from tailbone import forms
|
||||||
|
from tailbone.db import Session
|
||||||
|
from tailbone.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectFieldRenderer(formalchemy.FieldRenderer):
|
||||||
|
|
||||||
|
def render_readonly(self, **kwargs):
|
||||||
|
subject = self.raw_value
|
||||||
|
if not subject:
|
||||||
|
return ''
|
||||||
|
return tags.link_to(subject, self.request.route_url('messages.view', uuid=self.field.parent.model.uuid))
|
||||||
|
|
||||||
|
|
||||||
|
class SenderFieldRenderer(forms.renderers.UserFieldRenderer):
|
||||||
|
|
||||||
|
def render_readonly(self, **kwargs):
|
||||||
|
sender = self.raw_value
|
||||||
|
if sender is self.request.user:
|
||||||
|
return 'you'
|
||||||
|
return super(SenderFieldRenderer, self).render_readonly(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RecipientsFieldRenderer(formalchemy.FieldRenderer):
|
||||||
|
|
||||||
|
def render_readonly(self, **kwargs):
|
||||||
|
recipients = self.raw_value
|
||||||
|
if not recipients:
|
||||||
|
return ''
|
||||||
|
recips = filter(lambda r: r.recipient is not self.request.user, recipients)
|
||||||
|
recips = sorted([r.recipient.display_name for r in recips])
|
||||||
|
if len(recips) < len(recipients):
|
||||||
|
recips.insert(0, 'you')
|
||||||
|
return ', '.join(recips)
|
||||||
|
|
||||||
|
|
||||||
|
class TerseRecipientsFieldRenderer(formalchemy.FieldRenderer):
|
||||||
|
|
||||||
|
def render_readonly(self, **kwargs):
|
||||||
|
recipients = self.raw_value
|
||||||
|
if not recipients:
|
||||||
|
return ''
|
||||||
|
recips = filter(lambda r: r.recipient is not self.request.user, recipients)
|
||||||
|
recips = sorted([r.recipient.display_name for r in recips])
|
||||||
|
if len(recips) < 5:
|
||||||
|
return ', '.join(recips)
|
||||||
|
return "{}, ...".format(', '.join(recips[:4]))
|
||||||
|
|
||||||
|
|
||||||
|
class MessagesView(MasterView):
|
||||||
|
"""
|
||||||
|
Base class for message views.
|
||||||
|
"""
|
||||||
|
model_class = model.Message
|
||||||
|
creatable = False
|
||||||
|
editable = False
|
||||||
|
deletable = False
|
||||||
|
checkboxes = True
|
||||||
|
|
||||||
|
def get_index_url(self):
|
||||||
|
# not really used, but necessary to make certain other code happy
|
||||||
|
return self.request.route_url('messages.inbox')
|
||||||
|
|
||||||
|
def index(self):
|
||||||
|
if not self.request.user:
|
||||||
|
raise httpexceptions.HTTPForbidden
|
||||||
|
return super(MessagesView, self).index()
|
||||||
|
|
||||||
|
def get_instance(self):
|
||||||
|
if not self.request.user:
|
||||||
|
raise httpexceptions.HTTPForbidden
|
||||||
|
message = super(MessagesView, self).get_instance()
|
||||||
|
if not self.associated_with(message):
|
||||||
|
raise httpexceptions.HTTPForbidden
|
||||||
|
return message
|
||||||
|
|
||||||
|
def associated_with(self, message):
|
||||||
|
if message.sender is self.request.user:
|
||||||
|
return True
|
||||||
|
for recip in message.recipients:
|
||||||
|
if recip.recipient is self.request.user:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def query(self, session):
|
||||||
|
return session.query(model.Message)\
|
||||||
|
.outerjoin(model.MessageRecipient)\
|
||||||
|
.filter(model.MessageRecipient.recipient == self.request.user)
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
|
||||||
|
g.joiners['sender'] = lambda q: q.join(model.User, model.User.uuid == model.Message.sender_uuid).outerjoin(model.Person)
|
||||||
|
g.filters['sender'] = g.make_filter('sender', model.Person.display_name,
|
||||||
|
default_active=True, default_verb='contains')
|
||||||
|
g.sorters['sender'] = g.make_sorter(model.Person.display_name)
|
||||||
|
|
||||||
|
g.filters['subject'].default_active = True
|
||||||
|
g.filters['subject'].default_verb = 'contains'
|
||||||
|
|
||||||
|
g.default_sortkey = 'sent'
|
||||||
|
g.default_sortdir = 'desc'
|
||||||
|
|
||||||
|
g.configure(
|
||||||
|
include=[
|
||||||
|
g.subject.with_renderer(SubjectFieldRenderer),
|
||||||
|
g.sender.with_renderer(SenderFieldRenderer).label("From"),
|
||||||
|
g.recipients.with_renderer(TerseRecipientsFieldRenderer).label("To"),
|
||||||
|
g.sent.with_renderer(forms.renderers.DateTimeFieldRenderer(self.rattail_config)),
|
||||||
|
],
|
||||||
|
readonly=True)
|
||||||
|
|
||||||
|
def row_attrs(self, row, i):
|
||||||
|
recip = self.get_recipient(row)
|
||||||
|
if recip:
|
||||||
|
return {'data-uuid': recip.uuid}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def configure_fieldset(self, fs):
|
||||||
|
fs.configure(
|
||||||
|
include=[
|
||||||
|
fs.subject,
|
||||||
|
fs.sender.with_renderer(SenderFieldRenderer).label("From"),
|
||||||
|
fs.recipients.with_renderer(RecipientsFieldRenderer).label("To"),
|
||||||
|
fs.sent.with_renderer(forms.renderers.DateTimeFieldRenderer(self.rattail_config)),
|
||||||
|
fs.body,
|
||||||
|
])
|
||||||
|
if self.viewing:
|
||||||
|
del fs.body
|
||||||
|
|
||||||
|
def get_recipient(self, message):
|
||||||
|
for recip in message.recipients:
|
||||||
|
if recip.recipient is self.request.user:
|
||||||
|
return recip
|
||||||
|
|
||||||
|
def template_kwargs_view(self, **kwargs):
|
||||||
|
message = kwargs['instance']
|
||||||
|
return {'message': message,
|
||||||
|
'recipient': self.get_recipient(message)}
|
||||||
|
|
||||||
|
def move(self):
|
||||||
|
"""
|
||||||
|
Move a message, either to the archive or back to the inbox.
|
||||||
|
"""
|
||||||
|
message = self.get_instance()
|
||||||
|
recipient = None
|
||||||
|
for recip in message.recipients:
|
||||||
|
if recip.recipient is self.request.user:
|
||||||
|
recipient = recip
|
||||||
|
break
|
||||||
|
if not recipient:
|
||||||
|
raise httpexceptions.HTTPForbidden
|
||||||
|
|
||||||
|
dest = self.request.GET.get('dest')
|
||||||
|
if dest not in ('inbox', 'archive'):
|
||||||
|
self.request.session.flash("Sorry, I couldn't make sense out of that request.")
|
||||||
|
return self.redirect(self.request.get_referrer(
|
||||||
|
default=self.request.route_url('messages_inbox')))
|
||||||
|
|
||||||
|
new_status = enum.MESSAGE_STATUS_INBOX if dest == 'inbox' else enum.MESSAGE_STATUS_ARCHIVE
|
||||||
|
if recipient.status != new_status:
|
||||||
|
recipient.status = new_status
|
||||||
|
return self.redirect(self.request.route_url('messages.{}'.format(
|
||||||
|
'archive' if dest == 'inbox' else 'inbox')))
|
||||||
|
|
||||||
|
def move_bulk(self):
|
||||||
|
"""
|
||||||
|
Move messages in bulk, to the archive or back to the inbox.
|
||||||
|
"""
|
||||||
|
dest = self.request.POST.get('destination', 'archive')
|
||||||
|
if self.request.method == 'POST':
|
||||||
|
uuids = self.request.POST.get('uuids', '').split(',')
|
||||||
|
if uuids:
|
||||||
|
new_status = enum.MESSAGE_STATUS_INBOX if dest == 'inbox' else enum.MESSAGE_STATUS_ARCHIVE
|
||||||
|
for uuid in uuids:
|
||||||
|
recip = Session.query(model.MessageRecipient).get(uuid) if uuid else None
|
||||||
|
if recip and recip.recipient is self.request.user:
|
||||||
|
if recip.status != new_status:
|
||||||
|
recip.status = new_status
|
||||||
|
route = 'messages.{}'.format('archive' if dest == 'inbox' else 'inbox')
|
||||||
|
return self.redirect(self.request.route_url(route))
|
||||||
|
|
||||||
|
|
||||||
|
class InboxView(MessagesView):
|
||||||
|
"""
|
||||||
|
Inbox message view.
|
||||||
|
"""
|
||||||
|
url_prefix = '/messages/inbox'
|
||||||
|
grid_key = 'messages.inbox'
|
||||||
|
|
||||||
|
def get_index_url(self):
|
||||||
|
return self.request.route_url('messages.inbox')
|
||||||
|
|
||||||
|
def query(self, session):
|
||||||
|
q = super(InboxView, self).query(session)
|
||||||
|
return q.filter(model.MessageRecipient.status == enum.MESSAGE_STATUS_INBOX)
|
||||||
|
|
||||||
|
|
||||||
|
class ArchiveView(MessagesView):
|
||||||
|
"""
|
||||||
|
Archived message view.
|
||||||
|
"""
|
||||||
|
url_prefix = '/messages/archive'
|
||||||
|
grid_key = 'messages.archive'
|
||||||
|
|
||||||
|
def get_index_url(self):
|
||||||
|
return self.request.route_url('messages.archive')
|
||||||
|
|
||||||
|
def query(self, session):
|
||||||
|
q = super(ArchiveView, self).query(session)
|
||||||
|
return q.filter(model.MessageRecipient.status == enum.MESSAGE_STATUS_ARCHIVE)
|
||||||
|
|
||||||
|
|
||||||
|
class SentView(MessagesView):
|
||||||
|
"""
|
||||||
|
Sent messages view.
|
||||||
|
"""
|
||||||
|
url_prefix = '/messages/sent'
|
||||||
|
grid_key = 'messages.sent'
|
||||||
|
checkboxes = False
|
||||||
|
|
||||||
|
def get_index_url(self):
|
||||||
|
return self.request.route_url('messages.sent')
|
||||||
|
|
||||||
|
def query(self, session):
|
||||||
|
return session.query(model.Message)\
|
||||||
|
.filter(model.Message.sender == self.request.user)
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
super(SentView, self).configure_grid(g)
|
||||||
|
g.filters['sender'].default_active = False
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
|
||||||
|
# inbox
|
||||||
|
config.add_route('messages.inbox', '/messages/inbox/')
|
||||||
|
config.add_view(InboxView, attr='index', route_name='messages.inbox')
|
||||||
|
|
||||||
|
# archive
|
||||||
|
config.add_route('messages.archive', '/messages/archive/')
|
||||||
|
config.add_view(ArchiveView, attr='index', route_name='messages.archive')
|
||||||
|
|
||||||
|
# move bulk
|
||||||
|
config.add_route('messages.move_bulk', '/messages/move-bulk')
|
||||||
|
config.add_view(MessagesView, attr='move_bulk', route_name='messages.move_bulk')
|
||||||
|
|
||||||
|
# sent
|
||||||
|
config.add_route('messages.sent', '/messages/sent/')
|
||||||
|
config.add_view(SentView, attr='index', route_name='messages.sent')
|
||||||
|
|
||||||
|
# view
|
||||||
|
config.add_route('messages.view', '/messages/{uuid}')
|
||||||
|
config.add_view(MessagesView, attr='view', route_name='messages.view')
|
||||||
|
|
||||||
|
# move (single)
|
||||||
|
config.add_route('messages.move', '/messages/{uuid}/move')
|
||||||
|
config.add_view(MessagesView, attr='move', route_name='messages.move')
|
Loading…
Reference in a new issue