You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by tv...@apache.org on 2013/11/08 05:45:47 UTC
[2/6] git commit: [#6694] ticket:461 Form to send message to a user
[#6694] ticket:461 Form to send message to a user
Project: http://git-wip-us.apache.org/repos/asf/incubator-allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-allura/commit/6ee08822
Tree: http://git-wip-us.apache.org/repos/asf/incubator-allura/tree/6ee08822
Diff: http://git-wip-us.apache.org/repos/asf/incubator-allura/diff/6ee08822
Branch: refs/heads/master
Commit: 6ee088220483d5f7a8d216fed7dfd5f475271c93
Parents: 38d7d8d
Author: Yuriy Arhipov <yu...@yandex.ru>
Authored: Fri Nov 1 13:35:41 2013 +0400
Committer: Tim Van Steenburgh <tv...@gmail.com>
Committed: Fri Nov 8 04:30:15 2013 +0000
----------------------------------------------------------------------
.../user_profile/templates/send_message.html | 69 ++++++++++++++++++++
.../ext/user_profile/templates/user_index.html | 4 +-
Allura/allura/ext/user_profile/user_main.py | 45 ++++++++++++-
Allura/allura/lib/mail_util.py | 4 +-
Allura/allura/model/auth.py | 23 +++++++
Allura/allura/tasks/mail_tasks.py | 5 +-
.../tests/functional/test_user_profile.py | 45 +++++++++++++
Allura/allura/tests/model/test_auth.py | 13 ++++
Allura/development.ini | 3 +
Allura/test.ini | 3 +
10 files changed, 209 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/ext/user_profile/templates/send_message.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/templates/send_message.html b/Allura/allura/ext/user_profile/templates/send_message.html
new file mode 100644
index 0000000..4dcec2d
--- /dev/null
+++ b/Allura/allura/ext/user_profile/templates/send_message.html
@@ -0,0 +1,69 @@
+{#-
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-#}
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}{{user.display_name}} / Profile{% endblock %}
+
+{% block header %}Send a Message{% endblock %}
+
+{% block head %}
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="feed.rss">
+ <link rel="alternate" type="application/atom+xml" title="Atom" href="feed.atom">
+{% endblock %}
+{% block actions %}
+ <a href="{{c.app.url}}feed.rss" title="Follow"><b data-icon="{{g.icons['feed'].char}}" class="ico {{g.icons['feed'].css}}"></b></a>
+{% endblock %}
+
+{% block content %}
+ {%if not expire_time%}
+ <div class="grid-24">
+ <div class="grid-24">
+ <b>To:</b> <a href="{{user.url()}}">{{user.display_name|default(user.username)}}</a>
+ </div>
+ <div class="grid-24">
+ <b>From:</b> {{c.user.email_address_header()}}
+ </div>
+ <div class="clear"> </div>
+ <form action="send_user_message" method="post">
+ <div class="grid-24">
+ <label for="subject"><b>Subject:</b></label>
+ </div>
+ <input class="grid-10" type="text" name="subject" id="subject">
+ <div class="grid-24"> </div>
+ <div class="grid-24">
+ <label for="message"><b>Message:</b></label>
+ </div>
+ <textarea class="grid-10" name="message" id="message" style="height:100px;"></textarea>
+ <div class="grid-24">
+ <input type="checkbox" name="cc" id="ccsender">
+ <label for="ccsender">CC Sender</label>
+ </div>
+ <div class="grid-24"> </div>
+ <div class="grid-24">
+ <input type="submit" value="Send Message">
+ </div>
+ </form>
+ </div>
+ {% else %}
+ <div class="grid-24">
+ <h2>Sorry, you can send an email after {{expire_time}}</h2>
+ </div>
+ {% endif %}
+{% endblock %}
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/ext/user_profile/templates/user_index.html
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/templates/user_index.html b/Allura/allura/ext/user_profile/templates/user_index.html
index 54cae11..f328085 100644
--- a/Allura/allura/ext/user_profile/templates/user_index.html
+++ b/Allura/allura/ext/user_profile/templates/user_index.html
@@ -228,7 +228,9 @@
</div>
</div>
{% endif %}
-
+ <div class="grid-24">
+ <b><a href="send_message">Send me a message</a></b>
+ </div>
</div><!-- end of Personal data section -->
<div class="grid-24">
<b>Current {{user.get_pref('display_name')}}'s skills list</b>
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/ext/user_profile/user_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index 09e436c..b4b76ab 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -22,8 +22,9 @@ import pkg_resources
from pylons import tmpl_context as c, app_globals as g
from pylons import request
from formencode import validators
-from tg import expose, redirect, validate, response
+from tg import expose, redirect, validate, response, config, flash
from webob import exc
+from datetime import timedelta, datetime
from allura import version
from allura.app import Application, SitemapEntry
@@ -106,3 +107,45 @@ class UserProfileController(BaseController, FeedController):
{'author_link': user.url()},
'Recent posts by %s' % user.display_name,
project.url())
+
+ @expose('jinja:allura.ext.user_profile:templates/send_message.html')
+ def send_message(self, subject='', message='', cc=None, errors=None):
+ user = c.project.user_project_of
+ if not user:
+ raise exc.HTTPNotFound()
+
+ time_interval = config['user_message.time_interval']
+ max_messages = config['user_message.max_messages']
+ expire_time = None
+
+ if not c.user.check_send_emails_times(time_interval, max_messages):
+ expire_seconds = c.user.send_emails_times[0] + timedelta(seconds=int(time_interval)) - datetime.utcnow()
+ h, remainder = divmod(expire_seconds.total_seconds(), 3600)
+ m, s = divmod(remainder, 60)
+ expire_time = '%s:%s:%s' % (int(h), int(m), int(s))
+
+ return dict(user=user, expire_time=expire_time)
+
+ @require_post()
+ @expose()
+ @validate(dict(subject=validators.NotEmpty,
+ message=validators.NotEmpty))
+ def send_user_message(self, subject='', message='', cc=None):
+
+ if c.form_errors:
+ error_msg = ''
+ for field, error in c.form_errors.iteritems():
+ error_msg += '%s: %s ' % (field, error)
+ flash(error_msg, 'error')
+ redirect(request.referer)
+ time_interval = config['user_message.time_interval']
+ max_messages = config['user_message.max_messages']
+ if cc:
+ cc = c.user.get_pref('email_address')
+ user = c.project.user_project_of
+ if c.user.check_send_emails_times(time_interval, max_messages):
+ c.user.send_user_message(user, subject, message, cc)
+ else:
+ flash("You can't send more than %s messages per %s seconds" % (max_messages, time_interval), 'error')
+ return redirect(user.url())
+
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/lib/mail_util.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py
index b3e3b11..b376c3d 100644
--- a/Allura/allura/lib/mail_util.py
+++ b/Allura/allura/lib/mail_util.py
@@ -202,7 +202,7 @@ class SMTPClient(object):
self._client = None
def sendmail(self, addrs, fromaddr, reply_to, subject, message_id, in_reply_to, message,
- sender=None, references=None):
+ sender=None, references=None, cc=None):
if not addrs: return
# We send one message with multiple envelope recipients, so use a generic To: addr
# It might be nice to refactor to send one message per recipient, and use the actual To: addr
@@ -213,6 +213,8 @@ class SMTPClient(object):
message['Message-ID'] = Header('<' + message_id + u'>')
if sender:
message['Sender'] = AddrHeader(sender)
+ if cc:
+ message['CC'] = AddrHeader(cc)
if in_reply_to:
if not isinstance(in_reply_to, basestring):
raise TypeError('Only strings are supported now, not lists')
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/model/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 2bc0ba3..63da96e 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -33,6 +33,7 @@ import iso8601
import pymongo
from pylons import tmpl_context as c, app_globals as g
from pylons import request
+from tg import flash
from ming import schema as S
from ming import Field, collection
@@ -333,6 +334,7 @@ class User(MappedClass, ActivityNode, ActivityObject):
end_time=dict(h=int, m=int))])
localization=FieldProperty(dict(city=str,country=str))
timezone=FieldProperty(str)
+ send_emails_times=FieldProperty([S.DateTime])
inactiveperiod=FieldProperty([dict(
start_date=S.DateTime,
end_date=S.DateTime)])
@@ -352,6 +354,27 @@ class User(MappedClass, ActivityNode, ActivityObject):
#Statistics
stats_id = FieldProperty(S.ObjectId, if_missing=None)
+ def check_send_emails_times(self, time_interval, max_messages):
+ times = []
+ time_interval = timedelta(seconds=int(time_interval))
+ for t in self.send_emails_times:
+ if t + time_interval > datetime.utcnow():
+ times.append(t)
+ self.send_emails_times = times
+ return len(times) < int(max_messages)
+
+ def send_user_message(self, user, subject, message, cc):
+ allura.tasks.mail_tasks.sendsimplemail.post(
+ toaddr=user.get_pref('email_address'),
+ fromaddr=c.user.get_pref('email_address'),
+ reply_to=c.user.get_pref('email_address'),
+ message_id=h.gen_message_id(),
+ subject=subject,
+ text=message,
+ cc=cc)
+ self.send_emails_times.append(datetime.utcnow())
+ flash("email sent")
+
@property
def activity_name(self):
return self.display_name or self.username
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/tasks/mail_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/mail_tasks.py b/Allura/allura/tasks/mail_tasks.py
index 9b7fbd5..8151d34 100644
--- a/Allura/allura/tasks/mail_tasks.py
+++ b/Allura/allura/tasks/mail_tasks.py
@@ -147,7 +147,8 @@ def sendsimplemail(
message_id,
in_reply_to=None,
sender=None,
- references=None):
+ references=None,
+ cc=None):
from allura import model as M
if fromaddr is None:
fromaddr = u'noreply@in.sf.net'
@@ -166,4 +167,4 @@ def sendsimplemail(
multi_msg = mail_util.make_multipart_message(plain_msg, html_msg)
smtp_client.sendmail(
[toaddr], fromaddr, reply_to, subject, message_id,
- in_reply_to, multi_msg, sender=sender, references=references)
+ in_reply_to, multi_msg, sender=sender, references=references, cc=cc)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/tests/functional/test_user_profile.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index e5a9aef..6730d6e 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -17,6 +17,8 @@
from formencode.variabledecode import variable_encode
+import mock
+
from allura.model import Project, User
from allura.tests import decorators as td
from allura.tests import TestController
@@ -57,3 +59,46 @@ class TestUserProfile(TestController):
r = self.app.get('/u/test-admin/profile/feed%s' % ext, status=200)
assert 'Recent posts by Test Admin' in r
assert 'Home modified by Test Admin' in r
+
+ @td.with_user_project('test-admin')
+ @td.with_user_project('test-user')
+ @mock.patch('allura.tasks.mail_tasks.sendsimplemail')
+ @mock.patch('allura.lib.helpers.gen_message_id')
+ @mock.patch('allura.model.User.check_send_emails_times')
+ def test_send_message(self, check, gen_message_id, sendsimplemail):
+ check.return_value = True
+ gen_message_id.return_value = 'id'
+ response = self.app.get('/u/test-user/profile/send_message', status=200)
+ assert '<b>From:</b> "Test Admin" <test-admin@users.localhost>' in response
+ self.app.post('/u/test-user/profile/send_user_message',
+ params={'subject': 'test subject',
+ 'message': 'test message',
+ 'cc': 'on'})
+
+ sendsimplemail.post.assert_called_once_with(
+ cc=User.by_username('test-admin').get_pref('email_address'),
+ text=u'test message',
+ toaddr=User.by_username('test-user').get_pref('email_address'),
+ fromaddr=User.by_username('test-admin').get_pref('email_address'),
+ reply_to=User.by_username('test-admin').get_pref('email_address'),
+ message_id=u'id',
+ subject=u'test subject')
+ sendsimplemail.reset_mock()
+ self.app.post('/u/test-user/profile/send_user_message',
+ params={'subject': 'test subject',
+ 'message': 'test message'})
+
+ sendsimplemail.post.assert_called_once_with(
+ cc=None,
+ text=u'test message',
+ toaddr=User.by_username('test-user').get_pref('email_address'),
+ fromaddr=User.by_username('test-admin').get_pref('email_address'),
+ reply_to=User.by_username('test-admin').get_pref('email_address'),
+ message_id=u'id',
+ subject=u'test subject')
+
+ check.return_value = False
+ response = self.app.get('/u/test-user/profile/send_message', status=200)
+ assert 'Sorry, you can send an email after' in response
+
+
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/allura/tests/model/test_auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index f8e7ed8..db57c18 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -24,6 +24,7 @@ from nose.tools import with_setup, assert_equal
from pylons import tmpl_context as c, app_globals as g
from webob import Request
from mock import patch
+from datetime import datetime, timedelta
from pymongo.errors import DuplicateKeyError
from ming.orm.ormsession import ThreadLocalORMSession
@@ -32,6 +33,7 @@ from allura import model as M
from allura.lib import plugin
from allura.tests import decorators as td
from alluratest.controller import setup_basic_test, setup_global_objects
+from allura.tasks import mail_tasks
def setUp():
@@ -225,3 +227,14 @@ def test_user_projects_by_role():
g.credentials.clear()
assert_equal(set(p.shortname for p in c.user.my_projects()), set(['test', 'test2', 'u/test-admin', 'adobe-1', '--init--']))
assert_equal(set(p.shortname for p in c.user.my_projects('Admin')), set(['test', 'u/test-admin', 'adobe-1', '--init--']))
+
+
+def test_check_send_emails_times():
+ user1 = M.User.register(dict(username='test-user'), make_project=False)
+ time1 = datetime.utcnow() - timedelta(minutes=30)
+ time2 = datetime.utcnow() - timedelta(minutes=45)
+ time3 = datetime.utcnow() - timedelta(minutes=70)
+ user1.send_emails_times = [time1, time2, time3]
+ assert not user1.check_send_emails_times(3600, 1)
+ assert_equal(len(user1.send_emails_times), 2)
+ assert user1.check_send_emails_times(3600, 3)
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 4a96528..2c701cb 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -272,6 +272,9 @@ forgeblog.exfeed = false
short_url.url_pattern = {base_url}/{nbhd}/{project}/{mount_point}/{short_name}
+user_message.time_interval = 3600
+user_message.max_messages = 20
+
[app:tool_test]
use = egg:Allura
override_root=basetest_project_root
http://git-wip-us.apache.org/repos/asf/incubator-allura/blob/6ee08822/Allura/test.ini
----------------------------------------------------------------------
diff --git a/Allura/test.ini b/Allura/test.ini
index 1931a92..8747433 100644
--- a/Allura/test.ini
+++ b/Allura/test.ini
@@ -121,6 +121,9 @@ short_url.url_pattern = {base_url}/{nbhd}/{project}/{mount_point}/{short_name}
# tests expect max length of 40000
markdown_render_max_length = 40000
+user_message.time_interval = 3600
+user_message.max_messages = 200
+
[app:main_without_authn]
use = main
skip_authentication = True