Changeset - cac5109ac3b6
[Not reviewed]
rhodecode/config/routing.py
Show inline comments
 
@@ -59,14 +59,14 @@ def make_map(config):
 
    #==========================================================================
 

	
 
    #MAIN PAGE
 
    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)
 
    rmap.connect('rst_help',
 
                 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
 
                 _static=True)
 
@@ -264,20 +264,47 @@ def make_map(config):
 
        m.connect("admin_setting", "/settings/{setting_id}",
 
                  action="show", conditions=dict(method=["GET"]))
 
        m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
 
                  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:
 
        m.connect('admin_home', '', action='index')
 
        m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
 
                  action='add_repo')
 
@@ -354,13 +381,13 @@ def make_map(config):
 
    rmap.connect('changeset_comment', '/{repo_name:.*}/changeset/{revision}/comment',
 
                controller='changeset', revision='tip', action='comment',
 
                conditions=dict(function=check_repo))
 

	
 
    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}',
 
                 controller='changeset', action='raw_changeset',
 
                 revision='tip', conditions=dict(function=check_repo))
 

	
rhodecode/controllers/admin/notifications.py
Show inline comments
 
new file 100644
 
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:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # 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:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # 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)
rhodecode/controllers/admin/settings.py
Show inline comments
 
@@ -369,20 +369,12 @@ class SettingsController(BaseController)
 
            log.error(traceback.format_exc())
 
            h.flash(_('error occurred during update of user %s') \
 
                    % form_result.get('username'), category='error')
 

	
 
        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):
 
        """GET /_admin/create_repository: Form to create a new item"""
 

	
 
        c.repo_groups = RepoGroup.groups_choices()
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -29,14 +29,13 @@ import traceback
 
from pylons import tmpl_context as c, url, request, response
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
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
 
from rhodecode.model.db import ChangesetComment
 
from rhodecode.model.comment import ChangesetCommentsModel
 

	
 
@@ -271,28 +270,27 @@ class ChangesetController(BaseRepoContro
 
        for x in c.changes:
 
            c.diffs += x[2]
 

	
 
        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))
 

	
 
    @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
 
        else:
 
            raise HTTPForbidden()
 

	
rhodecode/model/comment.py
Show inline comments
 
@@ -20,27 +20,39 @@
 
# 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 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__)
 

	
 

	
 
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):
 
        """
 
        Creates new comment for changeset
 
        
 
        :param text:
 
@@ -48,33 +60,43 @@ class ChangesetCommentsModel(BaseModel):
 
        :param user_id:
 
        :param revision:
 
        :param f_path:
 
        :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
 
            comment.f_path = f_path
 
            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):
 
        """
 
        Deletes given comment
 
        
rhodecode/model/db.py
Show inline comments
 
@@ -46,13 +46,12 @@ from rhodecode.lib.exceptions import Use
 
from rhodecode.lib.compat import json
 
from rhodecode.lib.caching_query import FromCache
 

	
 
from rhodecode.model.meta import Base, Session
 

	
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 
#==============================================================================
 
# BASE CLASSES
 
#==============================================================================
 

	
 
@@ -283,13 +282,15 @@ class User(Base, BaseModel):
 
    repositories = relationship('Repository')
 
    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
 
    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
 

	
 
    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):
 
        return '%s %s <%s>' % (self.name, self.lastname, self.email)
 

	
 
    @property
 
@@ -298,17 +299,15 @@ class User(Base, BaseModel):
 

	
 
    @property
 
    def is_admin(self):
 
        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):
 
        if case_insensitive:
 
            q = cls.query().filter(cls.username.ilike(username))
 
        else:
 
@@ -333,12 +332,13 @@ class User(Base, BaseModel):
 

	
 
        self.last_login = datetime.datetime.now()
 
        Session.add(self)
 
        Session.commit()
 
        log.debug('updated user %s lastlogin', self.username)
 

	
 

	
 
class UserLog(Base, BaseModel):
 
    __tablename__ = 'user_logs'
 
    __table_args__ = {'extend_existing':True}
 
    user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
 
    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
 
    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
 
@@ -1128,26 +1128,27 @@ class ChangesetComment(Base, BaseModel):
 

	
 

	
 
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)
 
    body = Column('body', Unicode(50000), nullable=True)
 
    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
 
    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
 
    def recipients(self):
 
        return [x.user for x in UserNotification.query()\
 
                .filter(UserNotification.notification == self).all()]
 
@@ -1155,36 +1156,43 @@ class Notification(Base, BaseModel):
 
    @classmethod
 
    def create(cls, created_by, subject, body, recipients, type_=None):
 
        if type_ is None:
 
            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'),
 
                      {'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)
 
    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,
 
                                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'
 
    __table_args__ = {'extend_existing':True}
 
    repository_id = Column('repository_id', String(250), primary_key=True)
 
    repository_path = Column('repository_path', Text)
rhodecode/model/meta.py
Show inline comments
 
@@ -12,13 +12,14 @@ cache_manager = cache.CacheManager()
 
__all__ = ['Base', 'Session']
 
#
 
# SQLAlchemy session manager. Updated by model.init_model()
 
#
 
Session = scoped_session(
 
                sessionmaker(
 
                    query_cls=caching_query.query_callable(cache_manager)
 
                    query_cls = caching_query.query_callable(cache_manager),
 
                    expire_on_commit = True,
 
                )
 
          )
 

	
 
# The declarative Base
 
Base = declarative_base()
 

	
rhodecode/model/notification.py
Show inline comments
 
@@ -26,21 +26,44 @@
 

	
 
import logging
 
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):
 
        """
 
        
 
        Creates notification of given type
 
        
 
@@ -52,40 +75,64 @@ class NotificationModel(BaseModel):
 
        :param type_: type of notification
 
        """
 

	
 
        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
rhodecode/public/css/style.css
Show inline comments
 
@@ -2600,13 +2600,14 @@ table#permissions_manage tr#add_perm_inp
 
 
div.gravatar {
 
	background-color: #FFF;
 
	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;
 
	border-radius: 6px;
 
}
 
 
@@ -3478,7 +3479,32 @@ form.comment-inline-form {
 
	color:#888 !important;
 
	display: block;
 
	font-size: 10px
 
}
 
.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
rhodecode/public/js/rhodecode.js
Show inline comments
 
@@ -560,6 +560,22 @@ var  getSelectionLink = function(selecti
 
	        else{
 
	        	YUD.setStyle('linktt','visibility','hidden');
 
	        }
 
	    }
 
	}
 
};
 

	
 
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);
 
};	
rhodecode/templates/admin/notifications/notifications.html
Show inline comments
 
file renamed from rhodecode/templates/admin/users/notifications.html to rhodecode/templates/admin/notifications/notifications.html
 
@@ -22,17 +22,39 @@
 
            <li>
 
              <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
 
            </li>          
 
        </ul>            
 
    </div>
 
    % if c.notifications:
 
    <%
 
    unread = lambda n:{False:'unread'}.get(n)
 
    %>
 
    <div class="table">
 
      %for notification in c.notifications:
 
          <div class="table">
 
            <h4>${notification.subject}</h4>
 
            <div>${h.rst(notification.body)}</div>
 
        <div id="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)}"/>
 
            </div>
 
            <div class="desc">
 
            <a href="${url('notification', notification_id=notification.notification_id)}">${notification.description}</a>
 
            </div>
 
            <div class="delete-notifications">
 
              <span id="${notification.notification_id}" class="delete-notification delete_icon action"></span>
 
            </div>
 
          </div>
 
          <div class="notification-subject">${h.urlify_text(notification.subject)}</div>
 
        </div>
 
      %endfor
 
    </div>
 
    %else:
 
        <div class="table">${_('No notifications here yet')}</div>
 
    %endif
 
</div>    
 
</div>
 
<script type="text/javascript">
 
var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
 
   YUE.on(YUQ('.delete-notification'),'click',function(e){
 
	   var notification_id = e.currentTarget.id;
 
	   deleteNotification(url,notification_id)
 
   })
 
</script>
 
</%def>  
rhodecode/templates/admin/notifications/show_notification.html
Show inline comments
 
new file 100644
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${_('Show notification')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(_('Notifications'),h.url('notifications'))}
 
    &raquo; 
 
    ${_('Show notification')}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('admin')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}       
 
        <ul class="links">
 
            <li>
 
              <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
 
            </li>          
 
        </ul>            
 
    </div>
 
    <div class="table">
 
      <div class="notification-header">
 
        <div class="gravatar">
 
            <img alt="gravatar" src="${h.gravatar_url(h.email(c.notification.created_by_user.email),24)}"/>
 
        </div>
 
        <div class="desc">
 
            ${c.notification.description}
 
        </div>
 
        <div class="delete-notifications">
 
          <span id="${c.notification.notification_id}" class="delete_icon action"></span>
 
        </div>
 
      </div>
 
      <div>${h.rst(c.notification.body)}</div>
 
    </div>
 
</div>
 
<script type="text/javascript">
 
var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
 
   YUE.on(YUQ('.delete-notification'),'click',function(e){
 
       var notification_id = e.currentTarget.id;
 
       deleteNotification(url,notification_id)
 
   })
 
</script>
 
</%def>  
rhodecode/templates/base/base.html
Show inline comments
 
@@ -50,13 +50,13 @@
 
              <a href="${h.url('public_journal')}">${_('Public journal')}</a>   
 
          %else:                        		            
 
          	<div style="float: left">
 
            ${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>
 
            <div class="notifications">
 
            <a href="${h.url('admin_settings_notifications')}">${c.unread_notifications}</a>
 
            <a href="${h.url('notifications')}">${c.unread_notifications}</a>
 
            </div>
 
          %endif
 
          </div>	
 
         </li>
 
         <li>
 
            <a href="${h.url('home')}">${_('Home')}</a>
rhodecode/tests/__init__.py
Show inline comments
 
@@ -6,24 +6,26 @@ command.
 

	
 
This module initializes the application via ``websetup`` (`paster
 
setup-app`) and provides the base testing objects.
 
"""
 
import os
 
import time
 
import logging
 
from os.path import join as jn
 

	
 
from unittest import TestCase
 

	
 
from paste.deploy import loadapp
 
from paste.script.appinstall import SetupCommand
 
from pylons import config, url
 
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'
 
time.tzset()
 

	
 
log = logging.getLogger(__name__)
 
@@ -65,21 +67,26 @@ class TestController(TestCase):
 
        self.sa = meta.Session
 
        self.index_location = config['app_conf']['index_dir']
 
        TestCase.__init__(self, *args, **kwargs)
 

	
 
    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))
 

	
 
        self.assertEqual(response.status, '302 Found')
 
        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])
 

	
rhodecode/tests/functional/test_admin_notifications.py
Show inline comments
 
new file 100644
 
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('''<div class="table">No notifications here yet</div>'''
 
                        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'))
 

	
rhodecode/tests/test_models.py
Show inline comments
 
@@ -158,64 +158,68 @@ class TestReposGroups(unittest.TestCase)
 

	
 
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')
 

	
 
        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)
 
        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(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)
 

	
 
        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)
0 comments (0 inline, 0 general)