# HG changeset patch # User Marcin Kuzminski # Date 2011-11-22 22:55:05 # Node ID cac5109ac3b6015ce571abbdd68570fdac7e9bda # Parent b369bec5d468bbe68c5eaeaa0d2c409727aacc29 Notification system improvements - deleting - tests - ui - moved to separate controller diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -62,8 +62,8 @@ def make_map(config): rmap.connect('home', '/', controller='home', action='index') rmap.connect('repo_switcher', '/repos', controller='home', action='repo_switcher') - rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}', - controller='home',action='branch_tag_switcher') + rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}', + controller='home', action='branch_tag_switcher') rmap.connect('bugtracker', "http://bitbucket.org/marcinkuzminski/rhodecode/issues", _static=True) @@ -267,14 +267,41 @@ def make_map(config): 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", action="create_repository", conditions=dict(method=["GET"])) + #NOTIFICATION REST ROUTES + with rmap.submapper(path_prefix=ADMIN_PREFIX, + controller='admin/notifications') as m: + m.connect("notifications", "/notifications", + action="create", conditions=dict(method=["POST"])) + m.connect("notifications", "/notifications", + action="index", conditions=dict(method=["GET"])) + m.connect("formatted_notifications", "/notifications.{format}", + action="index", conditions=dict(method=["GET"])) + m.connect("new_notification", "/notifications/new", + action="new", conditions=dict(method=["GET"])) + m.connect("formatted_new_notification", "/notifications/new.{format}", + action="new", conditions=dict(method=["GET"])) + m.connect("/notification/{notification_id}", + action="update", conditions=dict(method=["PUT"])) + m.connect("/notification/{notification_id}", + action="delete", conditions=dict(method=["DELETE"])) + m.connect("edit_notification", "/notification/{notification_id}/edit", + action="edit", conditions=dict(method=["GET"])) + m.connect("formatted_edit_notification", + "/notification/{notification_id}.{format}/edit", + action="edit", conditions=dict(method=["GET"])) + m.connect("notification", "/notification/{notification_id}", + action="show", conditions=dict(method=["GET"])) + m.connect("formatted_notification", "/notifications/{notification_id}.{format}", + action="show", conditions=dict(method=["GET"])) + + + #ADMIN MAIN PAGES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/admin') as m: @@ -357,7 +384,7 @@ def make_map(config): rmap.connect('changeset_comment_delete', '/{repo_name:.*}/changeset/comment/{comment_id}/delete', controller='changeset', action='delete_comment', - conditions = dict(function=check_repo, method=["DELETE"])) + conditions=dict(function=check_repo, method=["DELETE"])) rmap.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}', diff --git a/rhodecode/controllers/admin/notifications.py b/rhodecode/controllers/admin/notifications.py new file mode 100644 --- /dev/null +++ b/rhodecode/controllers/admin/notifications.py @@ -0,0 +1,84 @@ +import logging + +from pylons import tmpl_context as c + +from rhodecode.lib.base import BaseController, render +from rhodecode.model.db import Notification + +from rhodecode.model.notification import NotificationModel +from rhodecode.lib.auth import LoginRequired +from rhodecode.lib import helpers as h + +log = logging.getLogger(__name__) + +class NotificationsController(BaseController): + """REST Controller styled on the Atom Publishing Protocol""" + # To properly map this controller, ensure your config/routing.py + # file has a resource setup: + # map.resource('notification', 'notifications', controller='_admin/notifications', + # path_prefix='/_admin', name_prefix='_admin_') + + @LoginRequired() + def __before__(self): + super(NotificationsController, self).__before__() + + + def index(self, format='html'): + """GET /_admin/notifications: All items in the collection""" + # url('notifications') + c.user = self.rhodecode_user + c.notifications = NotificationModel()\ + .get_for_user(self.rhodecode_user.user_id) + return render('admin/notifications/notifications.html') + + def create(self): + """POST /_admin/notifications: Create a new item""" + # url('notifications') + + def new(self, format='html'): + """GET /_admin/notifications/new: Form to create a new item""" + # url('new_notification') + + def update(self, notification_id): + """PUT /_admin/notifications/id: Update an existing item""" + # Forms posted to this method should contain a hidden field: + # + # Or using helpers: + # h.form(url('notification', notification_id=ID), + # method='put') + # url('notification', notification_id=ID) + + def delete(self, notification_id): + """DELETE /_admin/notifications/id: Delete an existing item""" + # Forms posted to this method should contain a hidden field: + # + # Or using helpers: + # h.form(url('notification', notification_id=ID), + # method='delete') + # url('notification', notification_id=ID) + + no = Notification.get(notification_id) + owner = lambda: no.notifications_to_users.user.user_id == c.rhodecode_user.user_id + if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: + NotificationModel().delete(notification_id) + return 'ok' + return 'fail' + + def show(self, notification_id, format='html'): + """GET /_admin/notifications/id: Show a specific item""" + # url('notification', notification_id=ID) + c.user = self.rhodecode_user + c.notification = Notification.get(notification_id) + + unotification = NotificationModel()\ + .get_user_notification(c.user.user_id, + c.notification) + + if unotification.read is False: + unotification.mark_as_read() + + return render('admin/notifications/show_notification.html') + + def edit(self, notification_id, format='html'): + """GET /_admin/notifications/id/edit: Form to edit an existing item""" + # url('edit_notification', notification_id=ID) diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -372,14 +372,6 @@ class SettingsController(BaseController) 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): diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py +++ b/rhodecode/controllers/changeset.py @@ -32,8 +32,7 @@ from pylons.controllers.util import redi from pylons.decorators import jsonify import rhodecode.lib.helpers as h -from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \ - NotAnonymous +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.utils import EmptyChangeset from rhodecode.lib.compat import OrderedDict @@ -274,13 +273,12 @@ class ChangesetController(BaseRepoContro return render('changeset/raw_changeset.html') def comment(self, repo_name, revision): - ccmodel = ChangesetCommentsModel() - - ccmodel.create(text=request.POST.get('text'), - repo_id=c.rhodecode_db_repo.repo_id, - user_id=c.rhodecode_user.user_id, - revision=revision, f_path=request.POST.get('f_path'), - line_no=request.POST.get('line')) + ChangesetCommentsModel().create(text=request.POST.get('text'), + repo_id=c.rhodecode_db_repo.repo_id, + user_id=c.rhodecode_user.user_id, + revision=revision, + f_path=request.POST.get('f_path'), + line_no=request.POST.get('line')) return redirect(h.url('changeset_home', repo_name=repo_name, revision=revision)) @@ -288,8 +286,8 @@ class ChangesetController(BaseRepoContro @jsonify def delete_comment(self, comment_id): co = ChangesetComment.get(comment_id) - if (h.HasPermissionAny('hg.admin', 'repository.admin')() or - co.author.user_id == c.rhodecode_user.user_id): + owner = lambda : co.author.user_id == c.rhodecode_user.user_id + if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: ccmodel = ChangesetCommentsModel() ccmodel.delete(comment_id=comment_id) return True diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -23,13 +23,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - +import re import logging import traceback +from pylons.i18n.translation import _ +from sqlalchemy.util.compat import defaultdict + +from rhodecode.lib import helpers as h from rhodecode.model import BaseModel -from rhodecode.model.db import ChangesetComment, User, Notification -from sqlalchemy.util.compat import defaultdict +from rhodecode.model.db import ChangesetComment, User, Repository, Notification from rhodecode.model.notification import NotificationModel log = logging.getLogger(__name__) @@ -38,6 +41,15 @@ log = logging.getLogger(__name__) class ChangesetCommentsModel(BaseModel): + def _extract_mentions(self, s): + usrs = [] + for username in re.findall(r'(?:^@|\s@)(\w+)', s): + user_obj = User.get_by_username(username, case_insensitive=True) + if user_obj: + usrs.append(user_obj) + + return usrs + def create(self, text, repo_id, user_id, revision, f_path=None, line_no=None): """ @@ -51,8 +63,10 @@ class ChangesetCommentsModel(BaseModel): :param line_no: """ if text: + repo = Repository.get(repo_id) + desc = repo.scm_instance.get_changeset(revision).message comment = ChangesetComment() - comment.repo_id = repo_id + comment.repo = repo comment.user_id = user_id comment.revision = revision comment.text = text @@ -60,18 +74,26 @@ class ChangesetCommentsModel(BaseModel): comment.line_no = line_no self.sa.add(comment) - self.sa.commit() + self.sa.flush() # make notification - usr = User.get(user_id) - subj = 'User %s commented on %s' % (usr.username, revision) + line = '' + if line_no: + line = _('on line %s') % line_no + subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \ + {'commit_desc':desc,'line':line}, + h.url('changeset_home', repo_name=repo.repo_name, + revision = revision, + anchor = 'comment-%s' % comment.comment_id + ) + ) body = text recipients = ChangesetComment.get_users(revision=revision) + recipients += self._extract_mentions(body) NotificationModel().create(created_by=user_id, subject=subj, body = body, recipients = recipients, type_ = Notification.TYPE_CHANGESET_COMMENT) - return comment def delete(self, comment_id): diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -49,7 +49,6 @@ from rhodecode.lib.caching_query import from rhodecode.model.meta import Base, Session - log = logging.getLogger(__name__) #============================================================================== @@ -286,7 +285,9 @@ class User(Base, BaseModel): group_member = relationship('UsersGroupMember', cascade='all') - notifications = relationship('Notification', secondary='user_to_notification') + notifications = relationship('Notification', + secondary='user_to_notification', + order_by=lambda :Notification.created_on.desc()) @property def full_contact(self): @@ -301,11 +302,9 @@ class User(Base, BaseModel): return self.admin def __repr__(self): - try: - return "<%s('id:%s:%s')>" % (self.__class__.__name__, - self.user_id, self.username) - except: - return self.__class__.__name__ + return "<%s('id:%s:%s')>" % (self.__class__.__name__, + self.user_id, self.username) + @classmethod def get_by_username(cls, username, case_insensitive=False, cache=False): @@ -336,6 +335,7 @@ class User(Base, BaseModel): Session.commit() log.debug('updated user %s lastlogin', self.username) + class UserLog(Base, BaseModel): __tablename__ = 'user_logs' __table_args__ = {'extend_existing':True} @@ -1131,9 +1131,9 @@ class Notification(Base, BaseModel): __tablename__ = 'notifications' __table_args__ = ({'extend_existing':True}) - TYPE_CHANGESET_COMMENT = 'cs_comment' - TYPE_MESSAGE = 'message' - TYPE_MENTION = 'mention' + TYPE_CHANGESET_COMMENT = u'cs_comment' + TYPE_MESSAGE = u'message' + TYPE_MENTION = u'mention' notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) subject = Column('subject', Unicode(512), nullable=True) @@ -1142,9 +1142,10 @@ class Notification(Base, BaseModel): created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) type_ = Column('type', Unicode(256)) - create_by_user = relationship('User') - user_notifications = relationship('UserNotification', - primaryjoin = 'Notification.notification_id==UserNotification.notification_id', + created_by_user = relationship('User') + notifications_to_users = relationship('UserNotification', + primaryjoin='Notification.notification_id==UserNotification.notification_id', + lazy='joined', cascade = "all, delete, delete-orphan") @property @@ -1158,16 +1159,20 @@ class Notification(Base, BaseModel): type_ = Notification.TYPE_MESSAGE notification = cls() - notification.create_by_user = created_by + notification.created_by_user = created_by notification.subject = subject notification.body = body notification.type_ = type_ Session.add(notification) for u in recipients: u.notifications.append(notification) - Session.commit() return notification + @property + def description(self): + from rhodecode.model.notification import NotificationModel + return NotificationModel().make_description(self) + class UserNotification(Base, BaseModel): __tablename__ = 'user_to_notification' __table_args__ = (UniqueConstraint('user_id', 'notification_id'), @@ -1179,9 +1184,12 @@ class UserNotification(Base, BaseModel): 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") + notification = relationship('Notification', single_parent=True,) + def mark_as_read(self): + self.read = True + Session.add(self) + Session.commit() class DbMigrateVersion(Base, BaseModel): __tablename__ = 'db_migrate_version' diff --git a/rhodecode/model/meta.py b/rhodecode/model/meta.py --- a/rhodecode/model/meta.py +++ b/rhodecode/model/meta.py @@ -15,7 +15,8 @@ cache_manager = cache.CacheManager() # Session = scoped_session( sessionmaker( - query_cls=caching_query.query_callable(cache_manager) + query_cls = caching_query.query_callable(cache_manager), + expire_on_commit = True, ) ) diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py --- a/rhodecode/model/notification.py +++ b/rhodecode/model/notification.py @@ -29,15 +29,38 @@ import traceback from pylons.i18n.translation import _ -from rhodecode.lib import safe_unicode -from rhodecode.lib.caching_query import FromCache +from rhodecode.lib.helpers import age from rhodecode.model import BaseModel from rhodecode.model.db import Notification, User, UserNotification +log = logging.getLogger(__name__) class NotificationModel(BaseModel): + + def __get_user(self, user): + if isinstance(user, User): + return user + elif isinstance(user, basestring): + return User.get_by_username(username=user) + elif isinstance(user, int): + return User.get(user) + else: + raise Exception('Unsupported user must be one of int,' + 'str or User object') + + def __get_notification(self, notification): + if isinstance(notification, Notification): + return notification + elif isinstance(notification, int): + return Notification.get(notification) + else: + if notification: + raise Exception('notification must be int or Instance' + ' of Notification got %s' % type(notification)) + + def create(self, created_by, subject, body, recipients, type_=Notification.TYPE_MESSAGE): """ @@ -55,37 +78,61 @@ class NotificationModel(BaseModel): if not getattr(recipients, '__iter__', False): raise Exception('recipients must be a list of iterable') - created_by_obj = created_by - if not isinstance(created_by, User): - created_by_obj = User.get(created_by) - + created_by_obj = self.__get_user(created_by) recipients_objs = [] for u in recipients: - if isinstance(u, User): - recipients_objs.append(u) - elif isinstance(u, basestring): - recipients_objs.append(User.get_by_username(username=u)) - elif isinstance(u, int): - recipients_objs.append(User.get(u)) - else: - raise Exception('Unsupported recipient must be one of int,' - 'str or User object') - - Notification.create(created_by=created_by_obj, subject=subject, - body = body, recipients = recipients_objs, + recipients_objs.append(self.__get_user(u)) + recipients_objs = set(recipients_objs) + return Notification.create(created_by=created_by_obj, subject=subject, + body=body, recipients=recipients_objs, type_=type_) + def delete(self, notification_id): + # we don't want to remove actuall notification just the assignment + try: + notification_id = int(notification_id) + no = self.__get_notification(notification_id) + if no: + UserNotification.delete(no.notifications_to_users.user_to_notification_id) + return True + except Exception: + log.error(traceback.format_exc()) + raise 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.read == False)\ .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.read == False)\ .filter(UserNotification.user_id == user_id).all()] + + def get_user_notification(self, user, notification): + user = self.__get_user(user) + notification = self.__get_notification(notification) + + return UserNotification.query()\ + .filter(UserNotification.notification == notification)\ + .filter(UserNotification.user == user).scalar() + + def make_description(self, notification): + """ + Creates a human readable description based on properties + of notification object + """ + + _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'), + notification.TYPE_MESSAGE:_('sent message'), + notification.TYPE_MENTION:_('mentioned you')} + + tmpl = "%(user)s %(action)s %(when)s" + data = dict(user=notification.created_by_user.username, + action=_map[notification.type_], + when=age(notification.created_on)) + return tmpl % data diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -2603,7 +2603,8 @@ div.gravatar { border: 0px solid #D0D0D0; float: left; margin-right: 0.7em; - padding: 2px 2px 0; + padding: 2px 2px 2px 2px; + line-height:0; -webkit-border-radius: 6px; -khtml-border-radius: 6px; -moz-border-radius: 6px; @@ -3481,4 +3482,29 @@ form.comment-inline-form { } .notifications a:hover{ text-decoration: none !important; +} +.notification-header{ + +} +.notification-header .desc{ + font-size: 16px; + height: 24px; + padding-top: 6px; + float: left +} + +.notification-header .desc.unread{ + font-weight: bold; + font-size: 17px; +} + +.notification-header .delete-notifications{ + float: right; + padding-top: 8px; + cursor: pointer; +} +.notification-subject{ + clear:both; + border-bottom: 1px solid #eee; + padding:5px 0px 5px 38px; } \ No newline at end of file diff --git a/rhodecode/public/js/rhodecode.js b/rhodecode/public/js/rhodecode.js --- a/rhodecode/public/js/rhodecode.js +++ b/rhodecode/public/js/rhodecode.js @@ -563,3 +563,19 @@ var getSelectionLink = function(selecti } } }; + +var deleteNotification = function(url, notification_id){ + var callback = { + success:function(o){ + var obj = YUD.get(String("notification_"+notification_id)); + obj.parentNode.removeChild(obj); + }, + failure:function(o){ + alert("error"); + }, + }; + var postData = '_method=delete'; + var sUrl = url.replace('__NOTIFICATION_ID__',notification_id); + var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, + callback, postData); +}; diff --git a/rhodecode/templates/admin/users/notifications.html b/rhodecode/templates/admin/notifications/notifications.html rename from rhodecode/templates/admin/users/notifications.html rename to rhodecode/templates/admin/notifications/notifications.html --- a/rhodecode/templates/admin/users/notifications.html +++ b/rhodecode/templates/admin/notifications/notifications.html @@ -25,14 +25,36 @@ % if c.notifications: + <% + unread = lambda n:{False:'unread'}.get(n) + %> +
%for notification in c.notifications: -
-

${notification.subject}

-
${h.rst(notification.body)}
+
+
+
+ gravatar +
+ +
+ +
+
${h.urlify_text(notification.subject)}
+
%endfor +
%else:
${_('No notifications here yet')}
%endif -
+ + diff --git a/rhodecode/templates/admin/notifications/show_notification.html b/rhodecode/templates/admin/notifications/show_notification.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/notifications/show_notification.html @@ -0,0 +1,51 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Show notification')} ${c.rhodecode_user.username} - ${c.rhodecode_name} + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_('Notifications'),h.url('notifications'))} + » + ${_('Show notification')} + + +<%def name="page_nav()"> + ${self.menu('admin')} + + +<%def name="main()"> +
+ +
+ ${self.breadcrumbs()} + +
+
+
+
+ gravatar +
+
+ ${c.notification.description} +
+
+ +
+
+
${h.rst(c.notification.body)}
+
+
+ + diff --git a/rhodecode/templates/base/base.html b/rhodecode/templates/base/base.html --- a/rhodecode/templates/base/base.html +++ b/rhodecode/templates/base/base.html @@ -53,7 +53,7 @@ ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))} %endif diff --git a/rhodecode/tests/__init__.py b/rhodecode/tests/__init__.py --- a/rhodecode/tests/__init__.py +++ b/rhodecode/tests/__init__.py @@ -9,6 +9,7 @@ setup-app`) and provides the base testin """ import os import time +import logging from os.path import join as jn from unittest import TestCase @@ -20,7 +21,8 @@ from routes.util import URLGenerator from webtest import TestApp from rhodecode.model import meta -import logging +from rhodecode.model.db import User + import pylons.test os.environ['TZ'] = 'UTC' @@ -68,10 +70,11 @@ class TestController(TestCase): def log_user(self, username=TEST_USER_ADMIN_LOGIN, password=TEST_USER_ADMIN_PASS): + self._logged_username = username response = self.app.post(url(controller='login', action='index'), {'username':username, 'password':password}) - + if 'invalid user name' in response.body: self.fail('could not login using %s %s' % (username, password)) @@ -79,6 +82,10 @@ class TestController(TestCase): self.assertEqual(response.session['rhodecode_user'].username, username) return response.follow() + def _get_logged_user(self): + return User.get_by_username(self._logged_username) + + def checkSessionFlash(self, response, msg): self.assertTrue('flash' in response.session) self.assertTrue(msg in response.session['flash'][0][1]) diff --git a/rhodecode/tests/functional/test_admin_notifications.py b/rhodecode/tests/functional/test_admin_notifications.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/functional/test_admin_notifications.py @@ -0,0 +1,117 @@ +from rhodecode.tests import * +from rhodecode.model.db import Notification, User, UserNotification + +from rhodecode.model.user import UserModel +from rhodecode.model.notification import NotificationModel + +class TestNotificationsController(TestController): + + def test_index(self): + self.log_user() + + + u1 = UserModel().create_or_update(username='u1', password='qweqwe', + email='u1@rhodecode.org', + name='u1', lastname='u1').user_id + u2 = UserModel().create_or_update(username='u2', password='qweqwe', + email='u2@rhodecode.org', + name='u2', lastname='u2').user_id + + response = self.app.get(url('notifications')) + self.assertTrue('''
No notifications here yet
''' + in response.body) + + cur_user = self._get_logged_user() + + NotificationModel().create(created_by=u1, subject=u'test', + body=u'notification_1', + recipients=[cur_user]) + response = self.app.get(url('notifications')) + + self.assertTrue(u'notification_1' in response.body) + + User.delete(u1) + User.delete(u2) + +# def test_index_as_xml(self): +# response = self.app.get(url('formatted_notifications', format='xml')) +# +# def test_create(self): +# response = self.app.post(url('notifications')) +# +# def test_new(self): +# response = self.app.get(url('new_notification')) +# +# def test_new_as_xml(self): +# response = self.app.get(url('formatted_new_notification', format='xml')) +# +# def test_update(self): +# response = self.app.put(url('notification', notification_id=1)) +# +# def test_update_browser_fakeout(self): +# response = self.app.post(url('notification', notification_id=1), params=dict(_method='put')) + + def test_delete(self): + self.log_user() + cur_user = self._get_logged_user() + + u1 = UserModel().create_or_update(username='u1', password='qweqwe', + email='u1@rhodecode.org', + name='u1', lastname='u1') + u2 = UserModel().create_or_update(username='u2', password='qweqwe', + email='u2@rhodecode.org', + name='u2', lastname='u2') + + # make two notifications + notification = NotificationModel().create(created_by=cur_user, + subject=u'test', + body=u'hi there', + recipients=[cur_user, u1, u2]) + + u1 = User.get(u1.user_id) + u2 = User.get(u2.user_id) + + # check DB + self.assertEqual(u1.notifications, [notification]) + self.assertEqual(u2.notifications, [notification]) + cur_usr_id = cur_user.user_id + response = self.app.delete(url('notification', + notification_id=cur_usr_id)) + + cur_user = self._get_logged_user() + self.assertEqual(cur_user.notifications, []) + + User.delete(u1.user_id) + User.delete(u2.user_id) + + +# def test_delete_browser_fakeout(self): +# response = self.app.post(url('notification', notification_id=1), params=dict(_method='delete')) + + def test_show(self): + self.log_user() + cur_user = self._get_logged_user() + u1 = UserModel().create_or_update(username='u1', password='qweqwe', + email='u1@rhodecode.org', + name='u1', lastname='u1') + u2 = UserModel().create_or_update(username='u2', password='qweqwe', + email='u2@rhodecode.org', + name='u2', lastname='u2') + + notification = NotificationModel().create(created_by=cur_user, + subject='test', + body='hi there', + recipients=[cur_user, u1, u2]) + + response = self.app.get(url('notification', + notification_id=notification.notification_id)) + +# def test_show_as_xml(self): +# response = self.app.get(url('formatted_notification', notification_id=1, format='xml')) +# +# def test_edit(self): +# response = self.app.get(url('edit_notification', notification_id=1)) +# +# def test_edit_as_xml(self): +# response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml')) + diff --git a/rhodecode/tests/test_models.py b/rhodecode/tests/test_models.py --- a/rhodecode/tests/test_models.py +++ b/rhodecode/tests/test_models.py @@ -161,28 +161,35 @@ class TestNotifications(unittest.TestCas 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') - + self.u1 = UserModel().create_or_update(username=u'u1', password=u'qweqwe', + email=u'u1@rhodecode.org', + name=u'u1', lastname=u'u1') + self.u2 = UserModel().create_or_update(username=u'u2', password=u'qweqwe', + email=u'u2@rhodecode.org', + name=u'u2', lastname=u'u3') + self.u3 = UserModel().create_or_update(username=u'u3', password=u'qweqwe', + email=u'u3@rhodecode.org', + name=u'u3', lastname=u'u3') + def tearDown(self): + User.delete(self.u1.user_id) + User.delete(self.u2.user_id) + User.delete(self.u3.user_id) def test_create_notification(self): usrs = [self.u1, self.u2] notification = Notification.create(created_by=self.u1, - subject='subj', body='hi there', + subject=u'subj', body=u'hi there', recipients=usrs) + Session.commit() - notifications = Session.query(Notification).all() + + notifications = Notification.query().all() + self.assertEqual(len(notifications), 1) + 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.notification_id, notifications[0].notification_id) @@ -192,21 +199,23 @@ class TestNotifications(unittest.TestCas def test_user_notifications(self): notification1 = Notification.create(created_by=self.u1, - subject='subj', body='hi there', + subject=u'subj', body=u'hi there', recipients=[self.u3]) notification2 = Notification.create(created_by=self.u1, - subject='subj', body='hi there', + subject=u'subj', body=u'hi there', recipients=[self.u3]) self.assertEqual(self.u3.notifications, [notification1, notification2]) def test_delete_notifications(self): notification = Notification.create(created_by=self.u1, - subject='title', body='hi there3', + subject=u'title', body=u'hi there3', recipients=[self.u3, self.u1, self.u2]) + Session.commit() notifications = Notification.query().all() self.assertTrue(notification in notifications) Notification.delete(notification.notification_id) + Session.commit() notifications = Notification.query().all() self.assertFalse(notification in notifications) @@ -214,8 +223,3 @@ class TestNotifications(unittest.TestCas 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)