Add initial support for 'messages' views.
This commit is contained in:
		
							parent
							
								
									c2a6b0dd50
								
							
						
					
					
						commit
						84ebb158bc
					
				
					 6 changed files with 447 additions and 0 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar