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