changeset 1713:54687aa00724 beta

Tests updates, Session refactoring
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 23 Nov 2011 15:36:57 +0200
parents cac5109ac3b6
children 1308f068fc1a
files rhodecode/controllers/admin/notifications.py rhodecode/controllers/changeset.py rhodecode/lib/__init__.py rhodecode/lib/celerylib/tasks.py rhodecode/model/__init__.py rhodecode/model/comment.py rhodecode/model/db.py rhodecode/model/notification.py rhodecode/model/repo.py rhodecode/model/user.py rhodecode/model/users_group.py rhodecode/templates/admin/notifications/notifications.html rhodecode/tests/__init__.py rhodecode/tests/functional/test_admin_ldap_settings.py rhodecode/tests/functional/test_admin_notifications.py rhodecode/tests/functional/test_admin_repos.py rhodecode/tests/functional/test_admin_settings.py rhodecode/tests/functional/test_admin_users.py rhodecode/tests/functional/test_admin_users_groups.py rhodecode/tests/functional/test_journal.py rhodecode/tests/functional/test_login.py rhodecode/tests/functional/test_settings.py rhodecode/tests/functional/test_summary.py rhodecode/tests/test_libs.py rhodecode/tests/test_models.py
diffstat 25 files changed, 390 insertions(+), 215 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/admin/notifications.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/controllers/admin/notifications.py	Wed Nov 23 15:36:57 2011 +0200
@@ -1,6 +1,7 @@
 import logging
+import traceback
 
-from pylons import tmpl_context as c
+from pylons import tmpl_context as c, url
 
 from rhodecode.lib.base import BaseController, render
 from rhodecode.model.db import Notification
@@ -8,6 +9,8 @@
 from rhodecode.model.notification import NotificationModel
 from rhodecode.lib.auth import LoginRequired
 from rhodecode.lib import helpers as h
+from rhodecode.model.meta import Session
+from pylons.controllers.util import redirect
 
 log = logging.getLogger(__name__)
 
@@ -57,27 +60,42 @@
         #           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'
+        try:
+            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(c.rhodecode_user.user_id, no)
+                    Session.commit()
+                    return 'ok'
+        except Exception:
+            Session.rollback()
+            log.error(traceback.format_exc())
         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)
+        no = Notification.get(notification_id)
+
+        owner = lambda: (no.notifications_to_users.user.user_id
+                         == c.user.user_id)
+        if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
+            unotification = NotificationModel()\
+                            .get_user_notification(c.user.user_id, no)
 
-        unotification = NotificationModel()\
-                            .get_user_notification(c.user.user_id,
-                                                   c.notification)
+            # if this association to user is not valid, we don't want to show
+            # this message
+            if unotification:
+                if unotification.read is False:
+                    unotification.mark_as_read()
+                    Session.commit()
+                c.notification = no
 
-        if unotification.read is False:
-            unotification.mark_as_read()
+                return render('admin/notifications/show_notification.html')
 
-        return render('admin/notifications/show_notification.html')
+        return redirect(url('notifications'))
 
     def edit(self, notification_id, format='html'):
         """GET /_admin/notifications/id/edit: Form to edit an existing item"""
--- a/rhodecode/controllers/changeset.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/controllers/changeset.py	Wed Nov 23 15:36:57 2011 +0200
@@ -44,6 +44,7 @@
 from vcs.nodes import FileNode
 from vcs.utils import diffs as differ
 from webob.exc import HTTPForbidden
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
@@ -279,7 +280,7 @@
                                         revision=revision,
                                         f_path=request.POST.get('f_path'),
                                         line_no=request.POST.get('line'))
-
+        Session.commit()
         return redirect(h.url('changeset_home', repo_name=repo_name,
                               revision=revision))
 
@@ -288,8 +289,8 @@
         co = ChangesetComment.get(comment_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)
+            ChangesetCommentsModel().delete(comment=co)
+            Session.commit()
             return True
         else:
             raise HTTPForbidden()
--- a/rhodecode/lib/__init__.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/lib/__init__.py	Wed Nov 23 15:36:57 2011 +0200
@@ -24,6 +24,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
+import re
 
 def __get_lem():
     from pygments import lexers
@@ -78,9 +79,9 @@
 
 # extension together with weights to search lower is first
 RST_EXTS = [
-    ('', 0), ('.rst', 1),('.rest', 1),
-    ('.RST', 2) ,('.REST', 2), 
-    ('.txt', 3), ('.TXT', 3) 
+    ('', 0), ('.rst', 1), ('.rest', 1),
+    ('.RST', 2) , ('.REST', 2),
+    ('.txt', 3), ('.TXT', 3)
 ]
 
 MARKDOWN_EXTS = [
@@ -90,7 +91,7 @@
     ('.markdown', 4), ('.MARKDOWN', 4)
 ]
 
-PLAIN_EXTS = [('.text', 2),('.TEXT', 2)]
+PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
 
 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
 
@@ -223,7 +224,7 @@
     :rtype: str
     :returns: str object
     """
-    
+
     if not isinstance(unicode_, basestring):
         return str(unicode_)
 
@@ -436,3 +437,14 @@
                    "was: %s" % err)
         return None
 
+def extract_mentioned_users(s):
+    """
+    Returns unique usernames from given string s that have @mention
+    
+    :param s: string to get mentions
+    """
+    usrs = {}
+    for username in re.findall(r'(?:^@|\s@)(\w+)', s):
+        usrs[username] = username
+
+    return sorted(usrs.keys())
--- a/rhodecode/lib/celerylib/tasks.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/lib/celerylib/tasks.py	Wed Nov 23 15:36:57 2011 +0200
@@ -55,8 +55,6 @@
 
 add_cache(config)
 
-
-
 __all__ = ['whoosh_index', 'get_commits_stats',
            'reset_user_password', 'send_email']
 
@@ -101,6 +99,7 @@
 
     log.info('running task with lockkey %s', lockkey)
     try:
+        sa = get_session()
         lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
 
         #for js data compatibilty cleans the key for person from '
@@ -122,8 +121,6 @@
         last_cs = None
         timegetter = itemgetter('time')
 
-        sa = get_session()
-
         dbrepo = sa.query(Repository)\
             .filter(Repository.repo_name == repo_name).scalar()
         cur_stats = sa.query(Statistics)\
--- a/rhodecode/model/__init__.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/__init__.py	Wed Nov 23 15:36:57 2011 +0200
@@ -71,4 +71,22 @@
         if sa is not None:
             self.sa = sa
         else:
-            self.sa = meta.Session
+            self.sa = meta.Session()
+
+    def __get_instance(self, cls, instance):
+        """
+        Get's instance of given cls using some simple lookup mechanism
+        
+        :param cls: class to fetch
+        :param instance: int or Instance
+        """
+
+        if isinstance(instance, cls):
+            return instance
+        elif isinstance(instance, int) or str(instance).isdigit():
+            return cls.get(instance)
+        else:
+            if instance:
+                raise Exception('given object must be int or Instance'
+                                ' of %s got %s' % (type(cls),
+                                                   type(instance)))
--- a/rhodecode/model/comment.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/comment.py	Wed Nov 23 15:36:57 2011 +0200
@@ -23,13 +23,13 @@
 # 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 re
 import logging
 import traceback
 
 from pylons.i18n.translation import _
 from sqlalchemy.util.compat import defaultdict
 
+from rhodecode.lib import extract_mentioned_users
 from rhodecode.lib import helpers as h
 from rhodecode.model import BaseModel
 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
@@ -40,15 +40,16 @@
 
 class ChangesetCommentsModel(BaseModel):
 
+    def __get_changeset_comment(self, changeset_comment):
+        return self.__get_instance(ChangesetComment, changeset_comment)
 
     def _extract_mentions(self, s):
-        usrs = []
-        for username in re.findall(r'(?:^@|\s@)(\w+)', s):
+        user_objects = []
+        for username in extract_mentioned_users(s):
             user_obj = User.get_by_username(username, case_insensitive=True)
             if user_obj:
-                usrs.append(user_obj)
-
-        return usrs
+                user_objects.append(user_obj)
+        return user_objects
 
     def create(self, text, repo_id, user_id, revision, f_path=None,
                line_no=None):
@@ -81,30 +82,30 @@
             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},
+                                    {'commit_desc':desc, 'line':line},
                              h.url('changeset_home', repo_name=repo.repo_name,
-                                   revision = revision,
-                                   anchor = 'comment-%s' % comment.comment_id
+                                   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)
+                                   body=body, recipients=recipients,
+                                   type_=Notification.TYPE_CHANGESET_COMMENT)
 
             return comment
 
-    def delete(self, comment_id):
+    def delete(self, comment):
         """
         Deletes given comment
         
         :param comment_id:
         """
-        comment = ChangesetComment.get(comment_id)
+        comment = self.__get_changeset_comment(comment)
         self.sa.delete(comment)
-        self.sa.commit()
+
         return comment
 
 
--- a/rhodecode/model/db.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/db.py	Wed Nov 23 15:36:57 2011 +0200
@@ -48,7 +48,6 @@
 
 from rhodecode.model.meta import Base, Session
 
-
 log = logging.getLogger(__name__)
 
 #==============================================================================
@@ -122,7 +121,7 @@
 
     @classmethod
     def query(cls):
-        return Session.query(cls)
+        return Session().query(cls)
 
     @classmethod
     def get(cls, id_):
@@ -136,8 +135,7 @@
     @classmethod
     def delete(cls, id_):
         obj = cls.query().get(id_)
-        Session.delete(obj)
-        Session.commit()
+        Session().delete(obj)
 
 
 class RhodeCodeSetting(Base, BaseModel):
@@ -257,8 +255,8 @@
         new_ui.ui_key = key
         new_ui.ui_value = val
 
-        Session.add(new_ui)
-        Session.commit()
+        Session().add(new_ui)
+        Session().commit()
 
 
 class User(Base, BaseModel):
@@ -285,9 +283,7 @@
 
     group_member = relationship('UsersGroupMember', cascade='all')
 
-    notifications = relationship('Notification',
-                            secondary='user_to_notification',
-                            order_by=lambda :Notification.created_on.desc())
+    notifications = relationship('UserNotification')
 
     @property
     def full_contact(self):
@@ -331,8 +327,8 @@
         """Update user lastlogin"""
 
         self.last_login = datetime.datetime.now()
-        Session.add(self)
-        Session.commit()
+        Session().add(self)
+        Session().commit()
         log.debug('updated user %s lastlogin', self.username)
 
 
@@ -397,12 +393,12 @@
             for k, v in form_data.items():
                 setattr(new_users_group, k, v)
 
-            Session.add(new_users_group)
-            Session.commit()
+            Session().add(new_users_group)
+            Session().commit()
             return new_users_group
         except:
             log.error(traceback.format_exc())
-            Session.rollback()
+            Session().rollback()
             raise
 
     @classmethod
@@ -414,7 +410,7 @@
             for k, v in form_data.items():
                 if k == 'users_group_members':
                     users_group.members = []
-                    Session.flush()
+                    Session().flush()
                     members_list = []
                     if v:
                         v = [v] if isinstance(v, basestring) else v
@@ -424,11 +420,11 @@
                     setattr(users_group, 'members', members_list)
                 setattr(users_group, k, v)
 
-            Session.add(users_group)
-            Session.commit()
+            Session().add(users_group)
+            Session().commit()
         except:
             log.error(traceback.format_exc())
-            Session.rollback()
+            Session().rollback()
             raise
 
     @classmethod
@@ -445,11 +441,11 @@
                                                    assigned_groups)
 
             users_group = cls.get(users_group_id, cache=False)
-            Session.delete(users_group)
-            Session.commit()
+            Session().delete(users_group)
+            Session().commit()
         except:
             log.error(traceback.format_exc())
-            Session.rollback()
+            Session().rollback()
             raise
 
 class UsersGroupMember(Base, BaseModel):
@@ -472,8 +468,8 @@
         ugm = UsersGroupMember()
         ugm.users_group = group
         ugm.user = user
-        Session.add(ugm)
-        Session.commit()
+        Session().add(ugm)
+        Session().commit()
         return ugm
 
 class Repository(Base, BaseModel):
@@ -516,7 +512,7 @@
 
     @classmethod
     def get_by_repo_name(cls, repo_name):
-        q = Session.query(cls).filter(cls.repo_name == repo_name)
+        q = Session().query(cls).filter(cls.repo_name == repo_name)
         q = q.options(joinedload(Repository.fork))\
                 .options(joinedload(Repository.user))\
                 .options(joinedload(Repository.group))
@@ -533,7 +529,7 @@
 
         :param cls:
         """
-        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
                                               cls.url_sep())
         q.options(FromCache("sql_cache_short", "repository_repo_path"))
         return q.one().ui_value
@@ -569,7 +565,7 @@
         Returns base full path for that repository means where it actually
         exists on a filesystem
         """
-        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
+        q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
                                               Repository.url_sep())
         q.options(FromCache("sql_cache_short", "repository_repo_path"))
         return q.one().ui_value
@@ -886,10 +882,10 @@
         new.user_id = user_id
         new.permission = perm
         try:
-            Session.add(new)
-            Session.commit()
+            Session().add(new)
+            Session().commit()
         except:
-            Session.rollback()
+            Session().rollback()
 
 
     @classmethod
@@ -898,11 +894,12 @@
             raise Exception('perm needs to be an instance of Permission class')
 
         try:
-            cls.query().filter(cls.user_id == user_id)\
-                .filter(cls.permission == perm).delete()
-            Session.commit()
+            obj = cls.query().filter(cls.user_id == user_id)\
+                    .filter(cls.permission == perm).one()
+            Session().delete(obj)
+            Session().commit()
         except:
-            Session.rollback()
+            Session().rollback()
 
 class UsersGroupRepoToPerm(Base, BaseModel):
     __tablename__ = 'users_group_repo_to_perm'
@@ -948,10 +945,10 @@
         new.users_group_id = users_group_id
         new.permission = perm
         try:
-            Session.add(new)
-            Session.commit()
+            Session().add(new)
+            Session().commit()
         except:
-            Session.rollback()
+            Session().rollback()
 
 
     @classmethod
@@ -960,11 +957,12 @@
             raise Exception('perm needs to be an instance of Permission class')
 
         try:
-            cls.query().filter(cls.users_group_id == users_group_id)\
-                .filter(cls.permission == perm).delete()
-            Session.commit()
+            obj = cls.query().filter(cls.users_group_id == users_group_id)\
+                .filter(cls.permission == perm).one()
+            Session().delete(obj)
+            Session().commit()
         except:
-            Session.rollback()
+            Session().rollback()
 
 
 class UserRepoGroupToPerm(Base, BaseModel):
@@ -1077,11 +1075,11 @@
             inv_obj = CacheInvalidation(key)
 
         try:
-            Session.add(inv_obj)
-            Session.commit()
+            Session().add(inv_obj)
+            Session().commit()
         except Exception:
             log.error(traceback.format_exc())
-            Session.rollback()
+            Session().rollback()
 
     @classmethod
     def set_valid(cls, key):
@@ -1090,11 +1088,11 @@
         
         :param key:
         """
-        inv_obj = Session().query(CacheInvalidation)\
+        inv_obj = CacheInvalidation.query()\
             .filter(CacheInvalidation.cache_key == key).scalar()
         inv_obj.cache_active = True
-        Session.add(inv_obj)
-        Session.commit()
+        Session().add(inv_obj)
+        Session().commit()
 
 
 class ChangesetComment(Base, BaseModel):
@@ -1122,7 +1120,7 @@
         :param cls:
         :param revision:
         """
-        return Session.query(User)\
+        return Session().query(User)\
                 .filter(cls.revision == revision)\
                 .join(ChangesetComment.author).all()
 
@@ -1146,7 +1144,7 @@
     notifications_to_users = relationship('UserNotification',
         primaryjoin='Notification.notification_id==UserNotification.notification_id',
         lazy='joined',
-        cascade = "all, delete, delete-orphan")
+        cascade="all, delete, delete-orphan")
 
     @property
     def recipients(self):
@@ -1163,9 +1161,12 @@
         notification.subject = subject
         notification.body = body
         notification.type_ = type_
-        Session.add(notification)
+
         for u in recipients:
-            u.notifications.append(notification)
+            assoc = UserNotification()
+            assoc.notification = notification
+            u.notifications.append(assoc)
+        Session().add(notification)
         return notification
 
     @property
@@ -1177,19 +1178,17 @@
     __tablename__ = 'user_to_notification'
     __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
                       {'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)
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
+    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
     read = Column('read', Boolean, default=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,)
+    user = relationship('User', lazy="joined")
+    notification = relationship('Notification', lazy="joined", cascade='all')
 
     def mark_as_read(self):
         self.read = True
-        Session.add(self)
-        Session.commit()
+        Session().add(self)
 
 class DbMigrateVersion(Base, BaseModel):
     __tablename__ = 'db_migrate_version'
--- a/rhodecode/model/notification.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/notification.py	Wed Nov 23 15:36:57 2011 +0200
@@ -88,30 +88,35 @@
                             body=body, recipients=recipients_objs,
                             type_=type_)
 
-    def delete(self, notification_id):
-        # we don't want to remove actuall notification just the assignment
+    def delete(self, user, notification):
+        # we don't want to remove actual 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)
+            notification = self.__get_notification(notification)
+            user = self.__get_user(user)
+            if notification and user:
+                obj = UserNotification.query().filter(UserNotification.user == user)\
+                    .filter(UserNotification.notification == notification).one()
+                self.sa.delete(obj)
                 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_for_user(self, user):
+        user = self.__get_user(user)
+        return user.notifications
 
-    def get_unread_cnt_for_user(self, user_id):
+    def get_unread_cnt_for_user(self, user):
+        user = self.__get_user(user)
         return UserNotification.query()\
                 .filter(UserNotification.read == False)\
-                .filter(UserNotification.user_id == user_id).count()
+                .filter(UserNotification.user == user).count()
 
-    def get_unread_for_user(self, user_id):
+    def get_unread_for_user(self, user):
+        user = self.__get_user(user)
         return [x.notification for x in UserNotification.query()\
                 .filter(UserNotification.read == False)\
-                .filter(UserNotification.user_id == user_id).all()]
+                .filter(UserNotification.user == user).all()]
 
     def get_user_notification(self, user, notification):
         user = self.__get_user(user)
--- a/rhodecode/model/repo.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/repo.py	Wed Nov 23 15:36:57 2011 +0200
@@ -300,10 +300,11 @@
 
     def delete_perm_user(self, form_data, repo_name):
         try:
-            self.sa.query(UserRepoToPerm)\
+            obj = self.sa.query(UserRepoToPerm)\
                 .filter(UserRepoToPerm.repository \
                         == self.get_by_repo_name(repo_name))\
-                .filter(UserRepoToPerm.user_id == form_data['user_id']).delete()
+                .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
+            self.sa.delete(obj)
             self.sa.commit()
         except:
             log.error(traceback.format_exc())
@@ -312,11 +313,12 @@
 
     def delete_perm_users_group(self, form_data, repo_name):
         try:
-            self.sa.query(UsersGroupRepoToPerm)\
+            obj = self.sa.query(UsersGroupRepoToPerm)\
                 .filter(UsersGroupRepoToPerm.repository \
                         == self.get_by_repo_name(repo_name))\
                 .filter(UsersGroupRepoToPerm.users_group_id \
-                        == form_data['users_group_id']).delete()
+ == form_data['users_group_id']).one()
+            self.sa.delete(obj)
             self.sa.commit()
         except:
             log.error(traceback.format_exc())
@@ -325,9 +327,10 @@
 
     def delete_stats(self, repo_name):
         try:
-            self.sa.query(Statistics)\
-                .filter(Statistics.repository == \
-                        self.get_by_repo_name(repo_name)).delete()
+            obj = self.sa.query(Statistics)\
+                    .filter(Statistics.repository == \
+                        self.get_by_repo_name(repo_name)).one()
+            self.sa.delete(obj)
             self.sa.commit()
         except:
             log.error(traceback.format_exc())
--- a/rhodecode/model/user.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/user.py	Wed Nov 23 15:36:57 2011 +0200
@@ -353,7 +353,7 @@
         #======================================================================
         # fetch default permissions
         #======================================================================
-        default_user = self.get_by_username('default')
+        default_user = User.get_by_username('default')
 
         default_perms = self.sa.query(UserRepoToPerm, Repository, Permission)\
             .join((Repository, UserRepoToPerm.repository_id ==
--- a/rhodecode/model/users_group.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/model/users_group.py	Wed Nov 23 15:36:57 2011 +0200
@@ -35,23 +35,14 @@
 
 class UsersGroupModel(BaseModel):
 
+    def __get_users_group(self, users_group):
+        return self.__get_instance(UsersGroup, users_group)
+
     def get(self, users_group_id, cache = False):
-        users_group = UsersGroup.query()
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                          "get_users_group_%s" % users_group_id))
-        return users_group.get(users_group_id)
+        return UsersGroup.get(users_group_id)
 
     def get_by_name(self, name, cache = False, case_insensitive = False):
-        users_group = UsersGroup.query()
-        if case_insensitive:
-            users_group = users_group.filter(UsersGroup.users_group_name.ilike(name))
-        else:
-            users_group = users_group.filter(UsersGroup.users_group_name == name)
-        if cache:
-            users_group = users_group.options(FromCache("sql_cache_short",
-                                          "get_users_group_%s" % name))
-        return users_group.scalar()
+        return UsersGroup.get_by_group_name(name, cache, case_insensitive)
 
     def create(self, form_data):
         try:
@@ -67,6 +58,18 @@
             self.sa.rollback()
             raise
 
+
+    def create_(self, name, active=True):
+        new = UsersGroup()
+        new.users_group_name = name
+        new.users_group_active = active
+        self.sa.add(new)
+        return new
+
+    def delete(self, users_group):
+        obj = self.__get_users_group(users_group)
+        self.sa.delete(obj)
+
     def add_user_to_group(self, users_group, user):
         for m in users_group.members:
             u = m.user
--- a/rhodecode/templates/admin/notifications/notifications.html	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/templates/admin/notifications/notifications.html	Wed Nov 23 15:36:57 2011 +0200
@@ -30,19 +30,19 @@
     %>
     <div class="table">
       %for notification in c.notifications:
-        <div id="notification_${notification.notification_id}">
+        <div id="notification_${notification.notification.notification_id}">
           <div class="notification-header">
             <div class="gravatar">
-                <img alt="gravatar" src="${h.gravatar_url(h.email(notification.created_by_user.email),24)}"/>
+                <img alt="gravatar" src="${h.gravatar_url(h.email(notification.notification.created_by_user.email),24)}"/>
             </div>
-            <div class="desc">
-            <a href="${url('notification', notification_id=notification.notification_id)}">${notification.description}</a>
+            <div class="desc ${unread(notification.read)}">
+            <a href="${url('notification', notification_id=notification.notification.notification_id)}">${notification.notification.description}</a>
             </div>
             <div class="delete-notifications">
-              <span id="${notification.notification_id}" class="delete-notification delete_icon action"></span>
+              <span id="${notification.notification.notification_id}" class="delete-notification delete_icon action"></span>
             </div>
           </div>
-          <div class="notification-subject">${h.urlify_text(notification.subject)}</div>
+          <div class="notification-subject">${h.urlify_text(notification.notification.subject)}</div>
         </div>
       %endfor
     </div>
--- a/rhodecode/tests/__init__.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/__init__.py	Wed Nov 23 15:36:57 2011 +0200
@@ -64,7 +64,7 @@
 
         self.app = TestApp(wsgiapp)
         url._push_object(URLGenerator(config['routes.map'], environ))
-        self.sa = meta.Session
+        self.Session = meta.Session
         self.index_location = config['app_conf']['index_dir']
         TestCase.__init__(self, *args, **kwargs)
 
--- a/rhodecode/tests/functional/test_admin_ldap_settings.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_admin_ldap_settings.py	Wed Nov 23 15:36:57 2011 +0200
@@ -42,6 +42,7 @@
                     'ldap_attr_email':'test@example.com' })
 
         new_settings = RhodeCodeSetting.get_ldap_settings()
+        print new_settings
         self.assertEqual(new_settings['ldap_host'], u'dc.example.com',
                          'fail db write compare')
 
--- a/rhodecode/tests/functional/test_admin_notifications.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_admin_notifications.py	Wed Nov 23 15:36:57 2011 +0200
@@ -3,19 +3,23 @@
 
 from rhodecode.model.user import UserModel
 from rhodecode.model.notification import NotificationModel
+from rhodecode.model.meta import Session
 
 class TestNotificationsController(TestController):
 
+
+    def tearDown(self):
+        for n in Notification.query().all():
+            inst = Notification.get(n.notification_id)
+            Session().delete(inst)
+        Session().commit()
+
     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('''<div class="table">No notifications here yet</div>'''
@@ -23,15 +27,12 @@
 
         cur_user = self._get_logged_user()
 
-        NotificationModel().create(created_by=u1, subject=u'test',
+        NotificationModel().create(created_by=u1, subject=u'test_notification_1',
                                    body=u'notification_1',
                                    recipients=[cur_user])
+        Session().commit()
         response = self.app.get(url('notifications'))
-
-        self.assertTrue(u'notification_1' in response.body)
-
-        User.delete(u1)
-        User.delete(u2)
+        self.assertTrue(u'test_notification_1' in response.body)
 
 #    def test_index_as_xml(self):
 #        response = self.app.get(url('formatted_notifications', format='xml'))
@@ -62,27 +63,29 @@
                                                email='u2@rhodecode.org',
                                                name='u2', lastname='u2')
 
-        # make two notifications 
+        # make notifications
         notification = NotificationModel().create(created_by=cur_user,
                                                   subject=u'test',
                                                   body=u'hi there',
                                                   recipients=[cur_user, u1, u2])
-
+        Session().commit()
         u1 = User.get(u1.user_id)
         u2 = User.get(u2.user_id)
 
         # check DB
-        self.assertEqual(u1.notifications, [notification])
-        self.assertEqual(u2.notifications, [notification])
+        get_notif = lambda un:[x.notification for x in un]
+        self.assertEqual(get_notif(cur_user.notifications), [notification])
+        self.assertEqual(get_notif(u1.notifications), [notification])
+        self.assertEqual(get_notif(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, [])
+        response = self.app.delete(url('notification',
+                                       notification_id=
+                                       notification.notification_id))
 
-        User.delete(u1.user_id)
-        User.delete(u2.user_id)
+        cur_user = User.get(cur_usr_id)
+        self.assertEqual(cur_user.notifications, [])
 
 
 #    def test_delete_browser_fakeout(self):
@@ -100,7 +103,7 @@
 
         notification = NotificationModel().create(created_by=cur_user,
                                                   subject='test',
-                                                  body='hi there',
+                                                  body=u'hi there',
                                                   recipients=[cur_user, u1, u2])
 
         response = self.app.get(url('notification',
@@ -114,4 +117,3 @@
 #
 #    def test_edit_as_xml(self):
 #        response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml'))
-
--- a/rhodecode/tests/functional/test_admin_repos.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_admin_repos.py	Wed Nov 23 15:36:57 2011 +0200
@@ -36,7 +36,7 @@
         self.checkSessionFlash(response, 'created repository %s' % (repo_name))
 
         #test if the repo was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
+        new_repo = self.Session().query(Repository).filter(Repository.repo_name ==
                                                     repo_name).one()
 
         self.assertEqual(new_repo.repo_name, repo_name)
@@ -73,7 +73,7 @@
                                'created repository %s' % (repo_name_unicode))
 
         #test if the repo was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
+        new_repo = self.Session().query(Repository).filter(Repository.repo_name ==
                                                 repo_name_unicode).one()
 
         self.assertEqual(new_repo.repo_name, repo_name_unicode)
@@ -113,7 +113,7 @@
         assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
 
         #test if the fork was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name == repo_name).one()
+        new_repo = self.Session().query(Repository).filter(Repository.repo_name == repo_name).one()
 
         assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
         assert new_repo.description == description, 'wrong description'
@@ -162,7 +162,7 @@
                         response.session['flash'][0])
 
         #test if the repo was created in the database
-        new_repo = self.sa.query(Repository).filter(Repository.repo_name ==
+        new_repo = self.Session().query(Repository).filter(Repository.repo_name ==
                                                     repo_name).one()
 
         self.assertEqual(new_repo.repo_name, repo_name)
@@ -182,7 +182,7 @@
         response.follow()
 
         #check if repo was deleted from db
-        deleted_repo = self.sa.query(Repository).filter(Repository.repo_name
+        deleted_repo = self.Session().query(Repository).filter(Repository.repo_name
                                                         == repo_name).scalar()
 
         self.assertEqual(deleted_repo, None)
--- a/rhodecode/tests/functional/test_admin_settings.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_admin_settings.py	Wed Nov 23 15:36:57 2011 +0200
@@ -145,7 +145,7 @@
         response.follow()
 
         assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
-        user = self.sa.query(User).filter(User.username == 'test_admin').one()
+        user = self.Session().query(User).filter(User.username == 'test_admin').one()
         assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
         assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
         assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
@@ -171,7 +171,7 @@
         self.checkSessionFlash(response,
                                'Your account was updated successfully')
 
-        user = self.sa.query(User).filter(User.username == 'test_admin').one()
+        user = self.Session().query(User).filter(User.username == 'test_admin').one()
         assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
 
         assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
--- a/rhodecode/tests/functional/test_admin_users.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_admin_users.py	Wed Nov 23 15:36:57 2011 +0200
@@ -32,7 +32,7 @@
 
         assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
 
-        new_user = self.sa.query(User).filter(User.username == username).one()
+        new_user = self.Session().query(User).filter(User.username == username).one()
 
 
         assert new_user.username == username, 'wrong info about username'
@@ -66,7 +66,7 @@
         assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
 
         def get_user():
-            self.sa.query(User).filter(User.username == username).one()
+            self.Session().query(User).filter(User.username == username).one()
 
         self.assertRaises(NoResultFound, get_user), 'found user in database'
 
@@ -100,7 +100,7 @@
 
         response = response.follow()
 
-        new_user = self.sa.query(User).filter(User.username == username).one()
+        new_user = self.Session().query(User).filter(User.username == username).one()
         response = self.app.delete(url('user', id=new_user.user_id))
 
         assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
--- a/rhodecode/tests/functional/test_admin_users_groups.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_admin_users_groups.py	Wed Nov 23 15:36:57 2011 +0200
@@ -52,13 +52,13 @@
                                'created users group %s' % users_group_name)
 
 
-        gr = self.sa.query(UsersGroup)\
+        gr = self.Session().query(UsersGroup)\
                            .filter(UsersGroup.users_group_name ==
                                    users_group_name).one()
 
         response = self.app.delete(url('users_group', id=gr.users_group_id))
 
-        gr = self.sa.query(UsersGroup)\
+        gr = self.Session().query(UsersGroup)\
                            .filter(UsersGroup.users_group_name ==
                                    users_group_name).scalar()
 
--- a/rhodecode/tests/functional/test_journal.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_journal.py	Wed Nov 23 15:36:57 2011 +0200
@@ -16,10 +16,10 @@
 
     def test_stop_following_repository(self):
         session = self.log_user()
-#        usr = self.sa.query(User).filter(User.username == 'test_admin').one()
-#        repo = self.sa.query(Repository).filter(Repository.repo_name == HG_REPO).one()
+#        usr = self.Session().query(User).filter(User.username == 'test_admin').one()
+#        repo = self.Session().query(Repository).filter(Repository.repo_name == HG_REPO).one()
 #
-#        followings = self.sa.query(UserFollowing)\
+#        followings = self.Session().query(UserFollowing)\
 #            .filter(UserFollowing.user == usr)\
 #            .filter(UserFollowing.follows_repository == repo).all()
 #
--- a/rhodecode/tests/functional/test_login.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_login.py	Wed Nov 23 15:36:57 2011 +0200
@@ -192,7 +192,7 @@
         self.assertEqual(response.status , '302 Found')
         assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
 
-        ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
+        ret = self.Session().query(User).filter(User.username == 'test_regular4').one()
         assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
         assert check_password(password, ret.password) == True , 'password mismatch'
         assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
@@ -224,8 +224,8 @@
         new.name = name
         new.lastname = lastname
         new.api_key = generate_api_key(username)
-        self.sa.add(new)
-        self.sa.commit()
+        self.Session().add(new)
+        self.Session().commit()
 
         response = self.app.post(url(controller='login',
                                      action='password_reset'),
--- a/rhodecode/tests/functional/test_settings.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_settings.py	Wed Nov 23 15:36:57 2011 +0200
@@ -32,7 +32,7 @@
                       % (repo_name, fork_name) in response.session['flash'][0], 'No flash message about fork'
 
         #test if the fork was created in the database
-        fork_repo = self.sa.query(Repository).filter(Repository.repo_name == fork_name).one()
+        fork_repo = self.Session().query(Repository).filter(Repository.repo_name == fork_name).one()
 
         assert fork_repo.repo_name == fork_name, 'wrong name of repo name in new db fork repo'
         assert fork_repo.fork.repo_name == repo_name, 'wrong fork parrent'
--- a/rhodecode/tests/functional/test_summary.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/functional/test_summary.py	Wed Nov 23 15:36:57 2011 +0200
@@ -43,5 +43,5 @@
     def _enable_stats(self):
         r = Repository.get_by_repo_name(HG_REPO)
         r.enable_statistics = True
-        self.sa.add(r)
-        self.sa.commit()
+        self.Session().add(r)
+        self.Session().commit()
--- a/rhodecode/tests/test_libs.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/test_libs.py	Wed Nov 23 15:36:57 2011 +0200
@@ -103,3 +103,14 @@
         for case in test_cases:
             self.assertEqual(str2bool(case[0]), case[1])
 
+
+    def test_mention_extractor(self):
+        from rhodecode.lib import extract_mentioned_users
+        sample = ("@first hi there @marcink here's my email marcin@email.com "
+                  "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
+                  "@MARCIN    @maRCiN @2one_more22")
+        s = ['2one_more22', 'D', 'MARCIN', 'first', 'lukaszb',
+             'maRCiN', 'marcink', 'one']
+        self.assertEqual(s, extract_mentioned_users(sample))
+
+
--- a/rhodecode/tests/test_models.py	Wed Nov 23 00:55:05 2011 +0200
+++ b/rhodecode/tests/test_models.py	Wed Nov 23 15:36:57 2011 +0200
@@ -4,13 +4,14 @@
 
 from rhodecode.model.repos_group import ReposGroupModel
 from rhodecode.model.repo import RepoModel
-from rhodecode.model.db import RepoGroup, User, Notification, UserNotification
+from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
+    UsersGroup, UsersGroupMember
 from sqlalchemy.exc import IntegrityError
 from rhodecode.model.user import UserModel
 
-from rhodecode.model import meta
-
-Session = meta.Session()
+from rhodecode.model.meta import Session
+from rhodecode.model.notification import NotificationModel
+from rhodecode.model.users_group import UsersGroupModel
 
 class TestReposGroups(unittest.TestCase):
 
@@ -155,67 +156,108 @@
         # test repo
         self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
 
+class TestUser(unittest.TestCase):
+
+    def test_create_and_remove(self):
+        usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
+                                     email=u'u232@rhodecode.org',
+                                     name=u'u1', lastname=u'u1')
+        self.assertEqual(User.get_by_username(u'test_user'), usr)
+
+        # make users group
+        users_group = UsersGroupModel().create_('some_example_group')
+        Session().commit()
+        UsersGroupModel().add_user_to_group(users_group, usr)
+
+        self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
+        self.assertEqual(UsersGroupMember.query().count(), 1)
+        UserModel().delete(usr.user_id)
+
+        self.assertEqual(UsersGroupMember.query().all(), [])
+
 
 class TestNotifications(unittest.TestCase):
 
-
+    def __init__(self, methodName='runTest'):
+        self.u1 = UserModel().create_or_update(username=u'u1',
+                                        password=u'qweqwe',
+                                        email=u'u1@rhodecode.org',
+                                        name=u'u1', lastname=u'u1').user_id
+        self.u2 = UserModel().create_or_update(username=u'u2',
+                                        password=u'qweqwe',
+                                        email=u'u2@rhodecode.org',
+                                        name=u'u2', lastname=u'u3').user_id
+        self.u3 = UserModel().create_or_update(username=u'u3',
+                                        password=u'qweqwe',
+                                        email=u'u3@rhodecode.org',
+                                        name=u'u3', lastname=u'u3').user_id
+        super(TestNotifications, self).__init__(methodName=methodName)
 
-    def setUp(self):
-        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 _clean_notifications(self):
+        for n in Notification.query().all():
+            Session().delete(n)
+
+        Session().commit()
+        self.assertEqual(Notification.query().all(), [])
 
 
     def test_create_notification(self):
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
         usrs = [self.u1, self.u2]
-        notification = Notification.create(created_by=self.u1,
+        notification = NotificationModel().create(created_by=self.u1,
                                            subject=u'subj', body=u'hi there',
                                            recipients=usrs)
-        Session.commit()
-
-
+        Session().commit()
+        u1 = User.get(self.u1)
+        u2 = User.get(self.u2)
+        u3 = User.get(self.u3)
         notifications = Notification.query().all()
         self.assertEqual(len(notifications), 1)
 
         unotification = UserNotification.query()\
             .filter(UserNotification.notification == notification).all()
 
-        self.assertEqual(notifications[0].recipients, [self.u1, self.u2])
+        self.assertEqual(notifications[0].recipients, [u1, u2])
         self.assertEqual(notification.notification_id,
                          notifications[0].notification_id)
         self.assertEqual(len(unotification), len(usrs))
-        self.assertEqual([x.user.user_id for x in unotification],
-                         [x.user_id for x in usrs])
+        self.assertEqual([x.user.user_id for x in unotification], usrs)
+
+        self._clean_notifications()
 
     def test_user_notifications(self):
-        notification1 = Notification.create(created_by=self.u1,
-                                            subject=u'subj', body=u'hi there',
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        notification1 = NotificationModel().create(created_by=self.u1,
+                                            subject=u'subj', body=u'hi there1',
                                             recipients=[self.u3])
-        notification2 = Notification.create(created_by=self.u1,
-                                            subject=u'subj', body=u'hi there',
+        Session().commit()
+        notification2 = NotificationModel().create(created_by=self.u1,
+                                            subject=u'subj', body=u'hi there2',
                                             recipients=[self.u3])
-        self.assertEqual(self.u3.notifications, [notification1, notification2])
+        Session().commit()
+        u3 = Session().query(User).get(self.u3)
+
+        self.assertEqual(sorted([x.notification for x in u3.notifications]),
+                         sorted([notification2, notification1]))
+        self._clean_notifications()
 
     def test_delete_notifications(self):
-        notification = Notification.create(created_by=self.u1,
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        notification = NotificationModel().create(created_by=self.u1,
                                            subject=u'title', body=u'hi there3',
                                     recipients=[self.u3, self.u1, self.u2])
-        Session.commit()
+        Session().commit()
         notifications = Notification.query().all()
         self.assertTrue(notification in notifications)
 
         Notification.delete(notification.notification_id)
-        Session.commit()
+        Session().commit()
 
         notifications = Notification.query().all()
         self.assertFalse(notification in notifications)
@@ -223,3 +265,65 @@
         un = UserNotification.query().filter(UserNotification.notification
                                              == notification).all()
         self.assertEqual(un, [])
+
+        self._clean_notifications()
+    def test_delete_association(self):
+
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        notification = NotificationModel().create(created_by=self.u1,
+                                           subject=u'title', body=u'hi there3',
+                                    recipients=[self.u3, self.u1, self.u2])
+        Session().commit()
+
+        unotification = UserNotification.query()\
+                            .filter(UserNotification.notification ==
+                                    notification)\
+                            .filter(UserNotification.user_id == self.u3)\
+                            .scalar()
+
+        self.assertEqual(unotification.user_id, self.u3)
+
+        NotificationModel().delete(self.u3,
+                                   notification.notification_id)
+        Session().commit()
+
+        unotification = UserNotification.query()\
+                            .filter(UserNotification.notification ==
+                                    notification)\
+                            .filter(UserNotification.user_id == self.u3)\
+                            .scalar()
+
+        self.assertEqual(unotification, None)
+        self._clean_notifications()
+
+    def test_notification_counter(self):
+        self._clean_notifications()
+        self.assertEqual([], Notification.query().all())
+        self.assertEqual([], UserNotification.query().all())
+
+        NotificationModel().create(created_by=self.u1,
+                            subject=u'title', body=u'hi there_delete',
+                            recipients=[self.u3, self.u1])
+        Session().commit()
+
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u1), 1)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u2), 0)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u3), 1)
+
+        notification = NotificationModel().create(created_by=self.u1,
+                                           subject=u'title', body=u'hi there3',
+                                    recipients=[self.u3, self.u1, self.u2])
+        Session().commit()
+
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u1), 2)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u2), 1)
+        self.assertEqual(NotificationModel()
+                         .get_unread_cnt_for_user(self.u3), 2)
+        self._clean_notifications()