Refactor messages view to use master3
This commit is contained in:
		
							parent
							
								
									84ebf5d929
								
							
						
					
					
						commit
						c3fb86e391
					
				
					 5 changed files with 133 additions and 141 deletions
				
			
		|  | @ -331,13 +331,24 @@ class Form(object): | |||
| 
 | ||||
|         return fields | ||||
| 
 | ||||
|     def remove_field(self, key): | ||||
|         if key in self.fields: | ||||
|             self.fields.remove(key) | ||||
|     def insert_before(self, field, newfield): | ||||
|         self.fields.insert_before(field, newfield) | ||||
| 
 | ||||
|     def remove_fields(self, *args): | ||||
|     def insert_after(self, field, newfield): | ||||
|         self.fields.insert_after(field, newfield) | ||||
| 
 | ||||
|     def remove(self, *args): | ||||
|         for arg in args: | ||||
|             self.remove_field(arg) | ||||
|             if arg in self.fields: | ||||
|                 self.fields.remove(arg) | ||||
| 
 | ||||
|     # TODO: deprecare / remove this | ||||
|     def remove_field(self, key): | ||||
|         self.remove(key) | ||||
| 
 | ||||
|     # TODO: deprecare / remove this | ||||
|     def remove_fields(self, *args): | ||||
|         self.remove(*args) | ||||
| 
 | ||||
|     def make_schema(self): | ||||
|         if not self.model_class: | ||||
|  | @ -538,7 +549,7 @@ class Form(object): | |||
|         label = HTML.tag('label', self.get_label(field_name), for_=field_name) | ||||
|         field = self.render_field_value(field_name) or '' | ||||
|         field_div = HTML.tag('div', class_='field', c=field) | ||||
|         return HTML.tag('div', class_='field-wrapper {}'.format(field), c=label + field_div) | ||||
|         return HTML.tag('div', class_='field-wrapper {}'.format(field_name), c=label + field_div) | ||||
| 
 | ||||
|     def render_field_value(self, field_name): | ||||
|         record = self.model_instance | ||||
|  |  | |||
|  | @ -15,17 +15,17 @@ | |||
| 
 | ||||
|     // validate message before sending | ||||
|     function validate_message_form() { | ||||
|         var form = $('#new-message'); | ||||
|         var form = $('#deform'); | ||||
| 
 | ||||
|         if (! form.find('input[name="Message--recipients"]').val()) { | ||||
|         if (! form.find('input[name="recipients_"]').val()) { | ||||
|             alert("You must specify some recipient(s) for the message."); | ||||
|             $('.recipients input').data('ui-tagit').tagInput.focus(); | ||||
|             $('.recipients_ input').data('ui-tagit').tagInput.focus(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (! form.find('input[name="Message--subject"]').val()) { | ||||
|         if (! form.find('input[name="subject"]').val()) { | ||||
|             alert("You must provide a subject for the message."); | ||||
|             form.find('input[name="Message--subject"]').focus(); | ||||
|             form.find('input[name="subject"]').focus(); | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|  | @ -34,7 +34,7 @@ | |||
| 
 | ||||
|     $(function() { | ||||
| 
 | ||||
|         var recipients = $('.recipients input'); | ||||
|         var recipients = $('.recipients_ input'); | ||||
| 
 | ||||
|         recipients.tagit({ | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ | |||
| <%def name="extra_styles()"> | ||||
|   ${parent.extra_styles()} | ||||
|   <style type="text/css"> | ||||
|     .field-wrapper.recipients .everyone { | ||||
|     .recipients .everyone { | ||||
|         cursor: pointer; | ||||
|         display: none; | ||||
|     } | ||||
|  |  | |||
|  | @ -158,6 +158,7 @@ class MasterView3(MasterView2): | |||
| 
 | ||||
|     def save_create_form(self, form): | ||||
|         self.before_create(form) | ||||
|         with self.Session().no_autoflush: | ||||
|             obj = self.objectify(form, self.form_deserialized) | ||||
|             self.before_create_flush(obj, form) | ||||
|         self.Session.add(obj) | ||||
|  |  | |||
|  | @ -26,79 +26,21 @@ Message Views | |||
| 
 | ||||
| from __future__ import unicode_literals, absolute_import | ||||
| 
 | ||||
| import json | ||||
| import pytz | ||||
| import six | ||||
| 
 | ||||
| from rattail import enum | ||||
| from rattail.db import model | ||||
| from rattail.time import localtime | ||||
| 
 | ||||
| import formalchemy | ||||
| from formalchemy.helpers import text_field | ||||
| import colander | ||||
| from deform import widget as dfwidget | ||||
| from pyramid import httpexceptions | ||||
| from webhelpers2.html import tags, HTML | ||||
| 
 | ||||
| from tailbone import forms | ||||
| from tailbone.db import Session | ||||
| from tailbone.views import MasterView2 as MasterView | ||||
| from tailbone.views import MasterView3 as MasterView | ||||
| from tailbone.util import raw_datetime | ||||
| 
 | ||||
| 
 | ||||
| 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 RecipientsField(formalchemy.Field): | ||||
|     """ | ||||
|     Custom field for recipients, used when sending new messages. | ||||
|     """ | ||||
|     is_collection = True | ||||
| 
 | ||||
|     def sync(self): | ||||
|         if not self.is_readonly(): | ||||
|             message = self.parent.model | ||||
|             for uuid in self._deserialize(): | ||||
|                 user = Session.query(model.User).get(uuid) | ||||
|                 if user: | ||||
|                     message.add_recipient(user, status=enum.MESSAGE_STATUS_INBOX) | ||||
| 
 | ||||
| 
 | ||||
| class RecipientsFieldRenderer(formalchemy.FieldRenderer): | ||||
| 
 | ||||
|     def render(self, **kwargs): | ||||
|         uuids = self.value | ||||
|         value = ','.join(uuids) if uuids else '' | ||||
|         return text_field(self.name, value=value, **kwargs) | ||||
| 
 | ||||
|     def deserialize(self): | ||||
|         value = self.params.getone(self.name).split(',') | ||||
|         value = [uuid.strip() for uuid in value] | ||||
|         value = set([uuid for uuid in value if uuid]) | ||||
|         return value | ||||
| 
 | ||||
|     def render_readonly(self, **kwargs): | ||||
|         recipients = self.raw_value | ||||
|         if not recipients: | ||||
|             return '' | ||||
|         recips = [r for r in recipients if r.recipient is not self.request.user] | ||||
|         recips = sorted([r.recipient.display_name for r in recips]) | ||||
|         if len(recips) < len(recipients): | ||||
|             recips.insert(0, 'you') | ||||
|         max_display = 5 | ||||
|         if len(recips) > max_display: | ||||
|             basic = HTML.literal("{}, ".format(', '.join(recips[:max_display-1]))) | ||||
|             more = tags.link_to("({} more)".format(len(recips[max_display-1:])), '#', class_='more') | ||||
|             everyone = HTML.tag('span', class_='everyone', c=', '.join(recips[max_display-1:])) | ||||
|             return basic + more + everyone | ||||
|         return ', '.join(recips) | ||||
| 
 | ||||
| 
 | ||||
| class MessagesView(MasterView): | ||||
|     """ | ||||
|     Base class for message views. | ||||
|  | @ -109,7 +51,21 @@ class MessagesView(MasterView): | |||
|     checkboxes = True | ||||
|     replying = False | ||||
|     reply_header_sent_format = '%a %d %b %Y at %I:%M %p' | ||||
|     grid_columns = ['subject', 'sender', 'recipients', 'sent'] | ||||
| 
 | ||||
|     grid_columns = [ | ||||
|         'subject', | ||||
|         'sender', | ||||
|         'recipients', | ||||
|         'sent', | ||||
|     ] | ||||
| 
 | ||||
|     form_fields = [ | ||||
|         'sender', | ||||
|         'recipients', | ||||
|         'sent', | ||||
|         'subject', | ||||
|         'body', | ||||
|     ] | ||||
| 
 | ||||
|     def get_index_title(self): | ||||
|         if self.listing: | ||||
|  | @ -117,7 +73,7 @@ class MessagesView(MasterView): | |||
|         if self.viewing: | ||||
|             message = self.get_instance() | ||||
|             recipient = self.get_recipient(message) | ||||
|             if recipient and recipient.status == enum.MESSAGE_STATUS_ARCHIVE: | ||||
|             if recipient and recipient.status == self.enum.MESSAGE_STATUS_ARCHIVE: | ||||
|                 return "Message Archive" | ||||
|             elif not recipient: | ||||
|                 return "Sent Messages" | ||||
|  | @ -178,7 +134,7 @@ class MessagesView(MasterView): | |||
|     def render_sent(self, message, column_name): | ||||
|         return raw_datetime(self.rattail_config, message.sent) | ||||
| 
 | ||||
|     def render_sender(self, message, column_name): | ||||
|     def render_sender(self, message, field): | ||||
|         sender = message.sender | ||||
|         if sender is self.request.user: | ||||
|             return 'you' | ||||
|  | @ -197,50 +153,63 @@ class MessagesView(MasterView): | |||
|             return "{}, ...".format(', '.join(recips[:4])) | ||||
|         return "" | ||||
| 
 | ||||
|     def make_form(self, instance, **kwargs): | ||||
|         form = super(MessagesView, self).make_form(instance, **kwargs) | ||||
|         if self.creating: | ||||
|             form.id = 'new-message' | ||||
|             form.cancel_url = self.request.get_referrer(default=self.request.route_url('messages.inbox')) | ||||
|             form.create_label = "Send Message" | ||||
|         return form | ||||
|     def render_recipients_full(self, message, field): | ||||
|         recipients = message.recipients | ||||
|         if not recipients: | ||||
|             return "" | ||||
|         recips = [r for r in recipients | ||||
|                   if r.recipient is not self.request.user] | ||||
|         recips = sorted([r.recipient.display_name for r in recips]) | ||||
|         if len(recips) < len(recipients) and ( | ||||
|                 message.sender is not self.request.user or not recips): | ||||
|             recips.insert(0, 'you') | ||||
|         max_display = 5 | ||||
|         if len(recips) > max_display: | ||||
|             basic = HTML.literal("{}, ".format(', '.join(recips[:max_display-1]))) | ||||
|             more = tags.link_to("({} more)".format(len(recips[max_display-1:])), '#', class_='more') | ||||
|             everyone = HTML.tag('span', class_='everyone', c=', '.join(recips[max_display-1:])) | ||||
|             return basic + more + everyone | ||||
|         return ', '.join(recips) | ||||
| 
 | ||||
|     # TODO!! | ||||
|     # def make_form(self, instance, **kwargs): | ||||
|     #     form = super(MessagesView, self).make_form(instance, **kwargs) | ||||
|     #     if self.creating: | ||||
|     #         form.id = 'new-message' | ||||
|     #         form.cancel_url = self.request.get_referrer(default=self.request.route_url('messages.inbox')) | ||||
|     #         form.create_label = "Send Message" | ||||
|     #     return form | ||||
| 
 | ||||
|     def configure_form(self, f): | ||||
|         super(MessagesView, self).configure_form(f) | ||||
| 
 | ||||
|     def configure_fieldset(self, fs): | ||||
|         # TODO: A fair amount of this still seems hacky... | ||||
| 
 | ||||
|         f.set_renderer('sender', self.render_sender) | ||||
|         f.set_label('sender', "From") | ||||
| 
 | ||||
|         f.set_type('sent', 'datetime') | ||||
| 
 | ||||
|         f.set_renderer('recipients', self.render_recipients_full) | ||||
|         f.set_label('recipients', "To") | ||||
| 
 | ||||
|         f.set_widget('body', dfwidget.TextAreaWidget(cols=50, rows=15)) | ||||
| 
 | ||||
|         if self.creating: | ||||
|             f.remove('sender', 'sent') | ||||
| 
 | ||||
|             # Must create a new 'sender' field so that we can feed it the | ||||
|             # current user as default value, but prevent attaching user to the | ||||
|             # (new) underlying message instance...ugh | ||||
|             fs.append(formalchemy.Field('sender', value=self.request.user, | ||||
|                                         renderer=forms.renderers.UserFieldRenderer, | ||||
|                                         label="From", readonly=True)) | ||||
| 
 | ||||
|             # Sort of the same thing for recipients, although most of that logic is below. | ||||
|             fs.append(RecipientsField('recipients', label="To", renderer=RecipientsFieldRenderer)) | ||||
| 
 | ||||
|             fs.configure(include=[ | ||||
|                 fs.sender, | ||||
|                 fs.recipients, | ||||
|                 fs.subject, | ||||
|                 fs.body.textarea(size='50x15'), | ||||
|             ]) | ||||
| 
 | ||||
|             # We'll assign some properties directly on the new message; | ||||
|             # apparently that's safe and won't cause it to be committed. | ||||
|             # Notably, we can't assign the sender yet.  Also the actual | ||||
|             # recipients assignment is handled by that field's sync(). | ||||
|             message = fs.model | ||||
|             f.insert_after('recipients', 'recipients_') | ||||
|             f.remove('recipients') | ||||
|             f.set_node('recipients_', colander.SchemaNode(colander.Set())) | ||||
|             f.set_widget('recipients_', RecipientsWidget()) | ||||
|             f.set_label('recipients_', "To") | ||||
| 
 | ||||
|             if self.replying: | ||||
|                 old_message = self.get_instance() | ||||
|                 message.subject = "Re: {}".format(old_message.subject) | ||||
|                 message.body = self.get_reply_body(old_message) | ||||
|                 f.set_default('subject', "Re: {}".format(old_message.subject)) | ||||
|                 f.set_default('body', self.get_reply_body(old_message)) | ||||
| 
 | ||||
|                 # Determine an initial set of recipients, based on reply | ||||
|                 # method.  This value will be set to a 'pseudo' field to avoid | ||||
|                 # touching the new model instance and causing a crap commit. | ||||
|                 # Determine an initial set of recipients, based on reply method. | ||||
| 
 | ||||
|                 # If replying to all, massage the list a little so that the | ||||
|                 # current user is not listed, and the sender is listed first. | ||||
|  | @ -250,38 +219,37 @@ class MessagesView(MasterView): | |||
|                              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]) | ||||
|                     value = sorted(value.items(), key=lambda r: r[1]) | ||||
|                     value = [r[0] for r in value] | ||||
|                     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) | ||||
|                     f.set_default('recipients_', ','.join(value)) | ||||
| 
 | ||||
|                 # Just a normal reply, to sender only. | ||||
|                 elif self.filter_reply_recipient(old_message.sender): | ||||
|                     fs.recipients.set(value=[old_message.sender.uuid]) | ||||
|                     f.set_default('recipients_', old_message.sender.uuid) | ||||
| 
 | ||||
|                 # Set focus to message body instead of recipients, when replying. | ||||
|                 fs.focus = fs.body | ||||
|                 # TODO? | ||||
|                 # # Set focus to message body instead of recipients, when replying. | ||||
|                 # fs.focus = fs.body | ||||
| 
 | ||||
|         elif self.viewing: | ||||
|             f.remove('body') | ||||
| 
 | ||||
|             # Viewing an existing message is a heck of a lot easier... | ||||
|             fs.configure(include=[ | ||||
|                 fs.sender.with_renderer(SenderFieldRenderer).label("From"), | ||||
|                 fs.recipients.with_renderer(RecipientsFieldRenderer).label("To"), | ||||
|                 fs.sent, | ||||
|                 fs.subject, | ||||
|             ]) | ||||
|     def objectify(self, form, data): | ||||
|         message = super(MessagesView, self).objectify(form, data) | ||||
| 
 | ||||
|     def before_create(self, form): | ||||
|         """ | ||||
|         This is where we must assign the current user as sender for new | ||||
|         messages, for now.  I'm still not quite happy with this... | ||||
|         """ | ||||
|         super(MessagesView, self).before_create(form) | ||||
|         message = form.fieldset.model | ||||
|         if self.creating: | ||||
|             if self.request.user: | ||||
|                 message.sender = self.request.user | ||||
| 
 | ||||
|             for uuid in data['recipients_']: | ||||
|                 user = self.Session.query(model.User).get(uuid) | ||||
|                 if user: | ||||
|                     message.add_recipient(user, status=self.enum.MESSAGE_STATUS_INBOX) | ||||
| 
 | ||||
|         return message | ||||
| 
 | ||||
|     def flash_after_create(self, obj): | ||||
|         self.request.session.flash("Message has been sent: {}".format( | ||||
|             self.get_instance_title(obj))) | ||||
|  | @ -290,8 +258,7 @@ class MessagesView(MasterView): | |||
|         return user.active | ||||
| 
 | ||||
|     def get_reply_header(self, message): | ||||
|         sent = pytz.utc.localize(message.sent) | ||||
|         sent = localtime(self.rattail_config, sent) | ||||
|         sent = localtime(self.rattail_config, message.sent, from_utc=True) | ||||
|         sent = sent.strftime(self.reply_header_sent_format) | ||||
|         return "On {}, {} wrote:".format(sent, message.sender.person.display_name) | ||||
| 
 | ||||
|  | @ -318,7 +285,6 @@ class MessagesView(MasterView): | |||
| 
 | ||||
|     def template_kwargs_create(self, **kwargs): | ||||
|         kwargs['available_recipients'] = self.get_available_recipients() | ||||
|         kwargs['json'] = json | ||||
|         if self.replying: | ||||
|             kwargs['original_message'] = self.get_instance() | ||||
|         return kwargs | ||||
|  | @ -371,7 +337,7 @@ class MessagesView(MasterView): | |||
|             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 | ||||
|         new_status = self.enum.MESSAGE_STATUS_INBOX if dest == 'inbox' else self.enum.MESSAGE_STATUS_ARCHIVE | ||||
|         if recipient.status != new_status: | ||||
|             recipient.status = new_status | ||||
|         return self.redirect(self.request.route_url('messages.{}'.format( | ||||
|  | @ -385,7 +351,7 @@ class MessagesView(MasterView): | |||
|         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 | ||||
|                 new_status = self.enum.MESSAGE_STATUS_INBOX if dest == 'inbox' else self.enum.MESSAGE_STATUS_ARCHIVE | ||||
|                 for uuid in uuids: | ||||
|                     recip = self.Session.query(model.MessageRecipient)\ | ||||
|                                         .filter(model.MessageRecipient.message_uuid == uuid)\ | ||||
|  | @ -436,7 +402,7 @@ class InboxView(MessagesView): | |||
| 
 | ||||
|     def query(self, session): | ||||
|         q = super(InboxView, self).query(session) | ||||
|         return q.filter(model.MessageRecipient.status == enum.MESSAGE_STATUS_INBOX) | ||||
|         return q.filter(model.MessageRecipient.status == self.enum.MESSAGE_STATUS_INBOX) | ||||
| 
 | ||||
| 
 | ||||
| class ArchiveView(MessagesView): | ||||
|  | @ -452,7 +418,7 @@ class ArchiveView(MessagesView): | |||
| 
 | ||||
|     def query(self, session): | ||||
|         q = super(ArchiveView, self).query(session) | ||||
|         return q.filter(model.MessageRecipient.status == enum.MESSAGE_STATUS_ARCHIVE) | ||||
|         return q.filter(model.MessageRecipient.status == self.enum.MESSAGE_STATUS_ARCHIVE) | ||||
| 
 | ||||
| 
 | ||||
| class SentView(MessagesView): | ||||
|  | @ -481,6 +447,20 @@ class SentView(MessagesView): | |||
|                                                 default_active=True, default_verb='contains') | ||||
| 
 | ||||
| 
 | ||||
| class RecipientsWidget(dfwidget.TextInputWidget): | ||||
| 
 | ||||
|     def deserialize(self, field, pstruct): | ||||
|         if pstruct is colander.null: | ||||
|             return [] | ||||
|         elif not isinstance(pstruct, six.string_types): | ||||
|             raise colander.Invalid(field.schema, "Pstruct is not a string") | ||||
|         if self.strip: | ||||
|             pstruct = pstruct.strip() | ||||
|         if not pstruct: | ||||
|             return [] | ||||
|         return pstruct.split(',') | ||||
| 
 | ||||
| 
 | ||||
| def includeme(config): | ||||
| 
 | ||||
|     config.add_tailbone_permission('messages', 'messages.list', "List/Search Messages") | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar