Changeset - f23828b00b21
[Not reviewed]
beta
0 6 0
Marcin Kuzminski - 14 years ago 2011-11-20 21:26:55
marcin@python-works.com
notification fixes and improvements
6 files changed with 97 insertions and 17 deletions:
0 comments (0 inline, 0 general)
rhodecode/model/comment.py
Show inline comments
 
@@ -7,80 +7,92 @@
 
    
 
    :created_on: Nov 11, 2011
 
    :author: marcink
 
    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 

	
 
import logging
 
import traceback
 

	
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import ChangesetComment
 
from rhodecode.model.db import ChangesetComment, User, Notification
 
from sqlalchemy.util.compat import defaultdict
 
from rhodecode.model.notification import NotificationModel
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetCommentsModel(BaseModel):
 

	
 

	
 
    def create(self, text, repo_id, user_id, revision, f_path=None,
 
               line_no=None):
 
        """
 
        Creates new comment for changeset
 
        
 
        :param text:
 
        :param repo_id:
 
        :param user_id:
 
        :param revision:
 
        :param f_path:
 
        :param line_no:
 
        """
 
        if text:
 
            comment = ChangesetComment()
 
            comment.repo_id = repo_id
 
            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()
 

	
 
            # make notification
 
            usr = User.get(user_id)
 
            subj = 'User %s commented on %s' % (usr.username, revision)
 
            body = text
 
            recipients = ChangesetComment.get_users(revision=revision)
 
            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
 
        
 
        :param comment_id:
 
        """
 
        comment = ChangesetComment.get(comment_id)
 
        self.sa.delete(comment)
 
        self.sa.commit()
 
        return comment
 

	
 

	
 
    def get_comments(self, repo_id, revision):
 
        return ChangesetComment.query()\
 
                .filter(ChangesetComment.repo_id == repo_id)\
 
                .filter(ChangesetComment.revision == revision)\
 
                .filter(ChangesetComment.line_no == None)\
 
                .filter(ChangesetComment.f_path == None).all()
 

	
 
    def get_inline_comments(self, repo_id, revision):
 
        comments = self.sa.query(ChangesetComment)\
 
            .filter(ChangesetComment.repo_id == repo_id)\
rhodecode/model/db.py
Show inline comments
 
@@ -1088,76 +1088,105 @@ class CacheInvalidation(Base, BaseModel)
 
        """
 
        Mark this cache key as active and currently cached
 
        
 
        :param key:
 
        """
 
        inv_obj = Session().query(CacheInvalidation)\
 
            .filter(CacheInvalidation.cache_key == key).scalar()
 
        inv_obj.cache_active = True
 
        Session.add(inv_obj)
 
        Session.commit()
 

	
 

	
 
class ChangesetComment(Base, BaseModel):
 
    __tablename__ = 'changeset_comments'
 
    __table_args__ = ({'extend_existing':True},)
 
    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
 
    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    revision = Column('revision', String(40), nullable=False)
 
    line_no = Column('line_no', Unicode(10), nullable=True)
 
    f_path = Column('f_path', Unicode(1000), nullable=True)
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
 
    text = Column('text', Unicode(25000), nullable=False)
 
    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
 

	
 
    author = relationship('User')
 
    author = relationship('User', lazy='joined')
 
    repo = relationship('Repository')
 

	
 

	
 
    @classmethod
 
    def get_users(cls, revision):
 
        """
 
        Returns user associated with this changesetComment. ie those
 
        who actually commented
 
        
 
        :param cls:
 
        :param revision:
 
        """
 
        return Session.query(User)\
 
                .filter(cls.revision == revision)\
 
                .join(ChangesetComment.author).all()
 

	
 

	
 
class Notification(Base, BaseModel):
 
    __tablename__ = 'notifications'
 
    __table_args__ = ({'extend_existing':True})
 

	
 
    TYPE_CHANGESET_COMMENT = 'cs_comment'
 
    TYPE_MESSAGE = 'message'
 
    TYPE_MENTION = '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',
 
        cascade = "all, delete, delete-orphan")
 

	
 
    @property
 
    def recipients(self):
 
        return [x.user for x in UserNotification.query()\
 
                .filter(UserNotification.notification == self).all()]
 

	
 
    @classmethod
 
    def create(cls, subject, body, recipients):
 
    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.subject = subject
 
        notification.body = body
 
        notification.type_ = type_
 
        Session.add(notification)
 
        for u in recipients:
 
            u.notifications.append(notification)
 
        Session.commit()
 
        return notification
 

	
 
class UserNotification(Base, BaseModel):
 
    __tablename__ = 'user_to_notification'
 
    __table_args__ = ({'extend_existing':True})
 
    __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")
 

	
 

	
 
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)
 
    version = Column('version', Integer)
 

	
rhodecode/model/notification.py
Show inline comments
 
@@ -17,50 +17,75 @@
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import logging
 
import traceback
 

	
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib import safe_unicode
 
from rhodecode.lib.caching_query import FromCache
 

	
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import Notification, User, UserNotification
 

	
 

	
 
class NotificationModel(BaseModel):
 

	
 
    def create(self, subject, body, recipients):
 
    def create(self, created_by, subject, body, recipients,
 
               type_=Notification.TYPE_MESSAGE):
 
        """
 
        
 
        Creates notification of given type
 
        
 
        :param created_by: int, str or User instance. User who created this
 
            notification
 
        :param subject:
 
        :param body:
 
        :param recipients: list of int, str or User objects
 
        :param type_: type of notification
 
        """
 

	
 
        if not getattr(recipients, '__iter__', False):
 
            raise Exception('recipients must be a list of iterable')
 

	
 
        for x in recipients:
 
            if not isinstance(x, User):
 
                raise Exception('recipient is not instance of %s got %s' % \
 
                                (User, type(x)))
 
        created_by_obj = created_by
 
        if not isinstance(created_by, User):
 
            created_by_obj = User.get(created_by)
 

	
 

	
 
        Notification.create(subject, body, recipients)
 
        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,
 
                            type_=type_)
 

	
 

	
 
    def get_for_user(self, user_id):
 
        return User.get(user_id).notifications
 

	
 
    def get_unread_cnt_for_user(self, user_id):
 
        return UserNotification.query()\
 
                .filter(UserNotification.sent_on == None)\
 
                .filter(UserNotification.user_id == user_id).count()
 

	
 
    def get_unread_for_user(self, user_id):
 
        return [x.notification for x in UserNotification.query()\
 
                .filter(UserNotification.sent_on == None)\
 
                .filter(UserNotification.user_id == user_id).all()]
rhodecode/public/css/diff.css
Show inline comments
 
div.diffblock {
 
    overflow: auto;
 
    padding: 0px;
 
    border: 1px solid #ccc;
 
    background: #f8f8f8;
 
    font-size: 100%;
 
    line-height: 100%;
 
    /* new */
 
    line-height: 125%;
 
        -webkit-border-radius: 6px 6px 0px 0px;
 
    -moz-border-radius: 6px 6px 0px 0px;
 
    border-radius: 6px 6px 0px 0px;     
 
}
 

	
 
div.diffblock.margined{
 
	margin: 0px 20px 0px 20px;
 
}
 

	
 
div.diffblock .code-header{
 
	border-bottom: 1px solid #CCCCCC;
 
	background: #EEEEEE;
 
	padding:10px 0 10px 0;
 
}
 
div.diffblock .code-header div{
 
	margin-left:25px;
 
	margin-left:10px;
 
	font-weight: bold;
 
	font-size: 14px;
 
}
 
div.diffblock .code-body{
 
	background: #FFFFFF;
 
}
 
div.diffblock pre.raw{
 
	background: #FFFFFF;
 
	color:#000000;
 
}
 

	
 
table.code-difftable{
 
	border-collapse: collapse;
 
	width: 99%;
 
}
 

	
 
table.code-difftable td {
 
    padding: 0 !important; 
 
    background: none !important; 
 
    border:0 !important;    
 
}
 

	
 

	
 
.code-difftable .context{
 
	background:none repeat scroll 0 0 #DDE7EF;
 
}
rhodecode/templates/admin/users/notifications.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

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

	
 
<%def name="breadcrumbs_links()">
 
    ${_('My Notifications')}
 
</%def>
 

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

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}       
 
        <ul class="links">
 
            <li>
 
              <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
 
            </li>          
 
        </ul>            
 
    </div>
 
    % for notification in c.notifications:
 
        ${notification.title}
 
    % if c.notifications:
 
      %for notification in c.notifications:
 
          <div class="table">
 
            <h4>${notification.subject}</h4>
 
            <div>${h.rst(notification.body)}</div>
 
          </div>
 
      %endfor
 
    %else:
 
        <div class="table">${_('No notifications here yet')}</div>
 
    %endfor
 
    %endif
 
</div>    
 
</%def>  
rhodecode/templates/changeset/changeset.html
Show inline comments
 
@@ -13,54 +13,54 @@
 
    &raquo;
 
    ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
 
</%def>
 

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

	
 
<%def name="fid(raw_id,path)" filter="strip">
 
  <% 
 
    return 'C-%s-%s' % (h.short_id(raw_id),h.safeid(h.safe_unicode(path))) 
 
  %>
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		<div class="diffblock">
 
			<div class="code-header">
 
				<div>
 
				${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
 
				 &raquo; <span>${h.link_to(_('raw diff'),
 
                <span>${h.link_to(_('raw diff'),
 
				h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
 
				 &raquo; <span>${h.link_to(_('download diff'),
 
				h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
 
				</div>
 
				<div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
                </div>
 
			</div>
 
		</div>
 
	    <div id="changeset_content">
 
			<div class="container">
 
	             <div class="left">
 
	                 <div class="date">${_('commit')} ${c.changeset.revision}: ${h.short_id(c.changeset.raw_id)}@${c.changeset.date}</div>
 
	                 <div class="author">
 
	                     <div class="gravatar">
 
	                         <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
 
	                     </div>
 
	                     <span>${h.person(c.changeset.author)}</span><br/>
 
	                     <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
 
	                 </div>
 
	                 <div class="message">${h.link_to(h.wrap_paragraphs(c.changeset.message),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</div>
 
	             </div>
 
	             <div class="right">
 
		             <div class="changes">
 
                        % if len(c.changeset.affected_files) <= c.affected_files_cut_off:	             
 
		                 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
 
		                 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
 
		                 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
 
	                    % else:
 
                         <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
0 comments (0 inline, 0 general)