Refactor messages view to use master3
This commit is contained in:
parent
84ebf5d929
commit
c3fb86e391
|
@ -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,8 +158,9 @@ class MasterView3(MasterView2):
|
|||
|
||||
def save_create_form(self, form):
|
||||
self.before_create(form)
|
||||
obj = self.objectify(form, self.form_deserialized)
|
||||
self.before_create_flush(obj, form)
|
||||
with self.Session().no_autoflush:
|
||||
obj = self.objectify(form, self.form_deserialized)
|
||||
self.before_create_flush(obj, form)
|
||||
self.Session.add(obj)
|
||||
self.Session.flush()
|
||||
return 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,37 +219,36 @@ 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
|
||||
message.sender = self.request.user
|
||||
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(
|
||||
|
@ -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…
Reference in a new issue