Mercurial > kallithea
changeset 1702:8cb7f5c4d494 beta
#302 - basic notification system, models+tests
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sun, 20 Nov 2011 01:53:00 +0200 |
parents | b702d0d4b030 |
children | f23828b00b21 |
files | rhodecode/config/routing.py rhodecode/controllers/admin/settings.py rhodecode/lib/base.py rhodecode/model/db.py rhodecode/model/notification.py rhodecode/public/css/style.css rhodecode/templates/admin/users/notifications.html rhodecode/templates/base/base.html rhodecode/tests/test_models.py |
diffstat | 9 files changed, 238 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/rhodecode/config/routing.py Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/config/routing.py Sun Nov 20 01:53:00 2011 +0200 @@ -267,6 +267,8 @@ action="show", conditions=dict(method=["GET"])) m.connect("admin_settings_my_account", "/my_account", action="my_account", conditions=dict(method=["GET"])) + m.connect("admin_settings_notifications", "/notifications", + action="notifications", conditions=dict(method=["GET"])) m.connect("admin_settings_my_account_update", "/my_account_update", action="my_account_update", conditions=dict(method=["PUT"])) m.connect("admin_settings_create_repository", "/create_repository",
--- a/rhodecode/controllers/admin/settings.py Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/controllers/admin/settings.py Sun Nov 20 01:53:00 2011 +0200 @@ -47,6 +47,7 @@ from rhodecode.model.scm import ScmModel from rhodecode.model.user import UserModel from rhodecode.model.db import User +from rhodecode.model.notification import NotificationModel log = logging.getLogger(__name__) @@ -371,6 +372,14 @@ return redirect(url('my_account')) + + @NotAnonymous() + def notifications(self): + c.user = User.get(self.rhodecode_user.user_id) + c.notifications = NotificationModel().get_for_user(c.user.user_id) + return render('admin/users/notifications.html'), + + @NotAnonymous() @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def create_repository(self):
--- a/rhodecode/lib/base.py Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/lib/base.py Sun Nov 20 01:53:00 2011 +0200 @@ -17,6 +17,7 @@ from rhodecode.model.scm import ScmModel from rhodecode import BACKENDS from rhodecode.model.db import Repository +from rhodecode.model.notification import NotificationModel log = logging.getLogger(__name__) @@ -29,6 +30,8 @@ c.ga_code = config.get('rhodecode_ga_code') c.repo_name = get_repo_slug(request) c.backends = BACKENDS.keys() + c.unread_notifications = NotificationModel()\ + .get_unread_cnt_for_user(c.rhodecode_user.user_id) self.cut_off_limit = int(config.get('cut_off_limit')) self.sa = meta.Session()
--- a/rhodecode/model/db.py Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/model/db.py Sun Nov 20 01:53:00 2011 +0200 @@ -286,6 +286,8 @@ group_member = relationship('UsersGroupMember', cascade='all') + notifications = relationship('Notification', secondary='user_to_notification') + @property def full_contact(self): return '%s %s <%s>' % (self.name, self.lastname, self.email) @@ -1111,6 +1113,47 @@ repo = relationship('Repository') +class Notification(Base, BaseModel): + __tablename__ = 'notifications' + __table_args__ = ({'extend_existing':True}) + notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) + subject = Column('subject', Unicode(512), nullable=True) + body = Column('body', Unicode(50000), nullable=True) + created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) + + user_notifications = relationship('UserNotification', + primaryjoin = 'Notification.notification_id==UserNotification.notification_id', + cascade = "all, delete, delete-orphan") + + @property + def recipients(self): + return [x.user for x in UserNotification.query()\ + .filter(UserNotification.notification == self).all()] + + @classmethod + def create(cls, subject, body, recipients): + notification = cls() + notification.subject = subject + notification.body = body + Session.add(notification) + for u in recipients: + u.notifications.append(notification) + Session.commit() + return notification + +class UserNotification(Base, BaseModel): + __tablename__ = 'user_to_notification' + __table_args__ = ({'extend_existing':True}) + user_to_notification_id = Column("user_to_notification_id", Integer(), nullable=False, unique=True, primary_key=True) + user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) + notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), nullable=False) + sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) + + user = relationship('User', single_parent=True, lazy="joined") + notification = relationship('Notification',single_parent=True, + cascade="all, delete, delete-orphan") + + class DbMigrateVersion(Base, BaseModel): __tablename__ = 'db_migrate_version' __table_args__ = {'extend_existing':True}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/model/notification.py Sun Nov 20 01:53:00 2011 +0200 @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.notification + ~~~~~~~~~~~~~~ + + Model for notifications + + + :created_on: Nov 20, 2011 + :author: marcink + :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> + :license: GPLv3, see COPYING for more details. +""" +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +import traceback + +from pylons.i18n.translation import _ + +from rhodecode.lib import safe_unicode +from rhodecode.lib.caching_query import FromCache + +from rhodecode.model import BaseModel +from rhodecode.model.db import Notification, User, UserNotification + + +class NotificationModel(BaseModel): + + def create(self, subject, body, recipients): + + if not getattr(recipients, '__iter__', False): + raise Exception('recipients must be a list of iterable') + + for x in recipients: + if not isinstance(x, User): + raise Exception('recipient is not instance of %s got %s' % \ + (User, type(x))) + + + Notification.create(subject, body, recipients) + + + def get_for_user(self, user_id): + return User.get(user_id).notifications + + def get_unread_cnt_for_user(self, user_id): + return UserNotification.query()\ + .filter(UserNotification.sent_on == None)\ + .filter(UserNotification.user_id == user_id).count() + + def get_unread_for_user(self, user_id): + return [x.notification for x in UserNotification.query()\ + .filter(UserNotification.sent_on == None)\ + .filter(UserNotification.user_id == user_id).all()]
--- a/rhodecode/public/css/style.css Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/public/css/style.css Sun Nov 20 01:53:00 2011 +0200 @@ -2600,7 +2600,7 @@ div.gravatar { background-color: #FFF; - border: 1px solid #D0D0D0; + border: 0px solid #D0D0D0; float: left; margin-right: 0.7em; padding: 2px 2px 0; @@ -3456,3 +3456,22 @@ color: #666; font-size: 16px; } +.notifications{ + width:22px; + padding:2px; + float:right; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + text-align: center; + margin: -1px -10px 0px 5px; + background-color: #DEDEDE; +} +.notifications a{ + color:#888 !important; + display: block; + font-size: 10px +} +.notifications a:hover{ + text-decoration: none !important; +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/admin/users/notifications.html Sun Nov 20 01:53:00 2011 +0200 @@ -0,0 +1,28 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('My Notifications')} ${c.rhodecode_user.username} - ${c.rhodecode_name} +</%def> + +<%def name="breadcrumbs_links()"> + ${_('My Notifications')} +</%def> + +<%def name="page_nav()"> + ${self.menu('admin')} +</%def> + +<%def name="main()"> +<div class="box"> + <!-- box / title --> + <div class="title"> + ${self.breadcrumbs()} + </div> + % for notification in c.notifications: + ${notification.title} + %else: + <div class="table">${_('No notifications here yet')}</div> + %endfor +</div> +</%def>
--- a/rhodecode/templates/base/base.html Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/templates/base/base.html Sun Nov 20 01:53:00 2011 +0200 @@ -50,6 +50,9 @@ <a href="${h.url('public_journal')}">${_('Public journal')}</a> %else: ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))} + <div class="notifications"> + <a href="${h.url('admin_settings_notifications')}">${c.unread_notifications}</a> + </div> %endif </div> </li>
--- a/rhodecode/tests/test_models.py Sat Nov 19 21:37:54 2011 +0200 +++ b/rhodecode/tests/test_models.py Sun Nov 20 01:53:00 2011 +0200 @@ -4,8 +4,13 @@ from rhodecode.model.repos_group import ReposGroupModel from rhodecode.model.repo import RepoModel -from rhodecode.model.db import RepoGroup, User +from rhodecode.model.db import RepoGroup, User, Notification, UserNotification from sqlalchemy.exc import IntegrityError +from rhodecode.model.user import UserModel + +from rhodecode.model import meta + +Session = meta.Session() class TestReposGroups(unittest.TestCase): @@ -151,3 +156,61 @@ self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name)) +class TestNotifications(unittest.TestCase): + + + + def setUp(self): + self.u1 = UserModel().create_or_update(username='u1', password='qweqwe', + email='u1@rhodecode.org', + name='u1', lastname='u1') + self.u2 = UserModel().create_or_update(username='u2', password='qweqwe', + email='u2@rhodecode.org', + name='u2', lastname='u3') + self.u3 = UserModel().create_or_update(username='u3', password='qweqwe', + email='u3@rhodecode.org', + name='u3', lastname='u3') + + + + def test_create_notification(self): + usrs = [self.u1, self.u2] + notification = Notification.create(subject='subj', body='hi there', + recipients=usrs) + + notifications = Session.query(Notification).all() + unotification = UserNotification.query()\ + .filter(UserNotification.notification == notification).all() + self.assertEqual(len(notifications), 1) + self.assertEqual(notifications[0].recipients, [self.u1, self.u2]) + self.assertEqual(notification, notifications[0]) + self.assertEqual(len(unotification), len(usrs)) + self.assertEqual([x.user.user_id for x in unotification], + [x.user_id for x in usrs]) + + def test_user_notifications(self): + notification1 = Notification.create(subject='subj', body='hi there', + recipients=[self.u3]) + notification2 = Notification.create(subject='subj', body='hi there', + recipients=[self.u3]) + self.assertEqual(self.u3.notifications, [notification1, notification2]) + + def test_delete_notifications(self): + notification = Notification.create(subject='title', body='hi there3', + recipients=[self.u3, self.u1, self.u2]) + notifications = Notification.query().all() + self.assertTrue(notification in notifications) + + Notification.delete(notification.notification_id) + + notifications = Notification.query().all() + self.assertFalse(notification in notifications) + + un = UserNotification.query().filter(UserNotification.notification + == notification).all() + self.assertEqual(un, []) + + def tearDown(self): + User.delete(self.u1.user_id) + User.delete(self.u2.user_id) + User.delete(self.u3.user_id)