Files @ 974d6470cbec
Branch filter:

Location: kallithea/kallithea/model/comment.py - annotation

Søren Løvborg
model: inline BaseModel.get_all

This is just needless indirection (and doesn't actually add any
abstraction on top of the database object), so inline all calls to it.

Don't touch PullRequestModel.get_all, though, since it is an entirely
unrelated method that just shadows the inherited get_all.
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
24c0d584ba86
d1addaf7a91e
1948ede028ef
1948ede028ef
d1addaf7a91e
d1addaf7a91e
1948ede028ef
ad38f9f93b3b
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
74b5e0318d4e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d51a6f5e57d1
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
72e0fe2e3278
d1addaf7a91e
5e7c69707f08
5e7c69707f08
5e7c69707f08
5e7c69707f08
f9bc28c44f30
0529498575ce
0529498575ce
f9bc28c44f30
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
0529498575ce
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
68011c4e6f33
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
3a221939a19f
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
f24851239566
f9bc28c44f30
0529498575ce
d1addaf7a91e
55f2f2b4c462
bd5d70b6f445
3a221939a19f
55f2f2b4c462
55f2f2b4c462
55f2f2b4c462
55f2f2b4c462
5e7c69707f08
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
55f2f2b4c462
3a221939a19f
5e7c69707f08
5e7c69707f08
5e7c69707f08
5e7c69707f08
5e7c69707f08
5e7c69707f08
5e7c69707f08
e30401bac6e1
f9bc28c44f30
d1addaf7a91e
196c46444d98
d1addaf7a91e
196c46444d98
d1addaf7a91e
0529498575ce
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
68011c4e6f33
d1addaf7a91e
d1addaf7a91e
20a094053606
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
bd5d70b6f445
196c46444d98
d1addaf7a91e
d1addaf7a91e
0529498575ce
f24851239566
f9bc28c44f30
f9bc28c44f30
3a221939a19f
3a221939a19f
3a221939a19f
3a221939a19f
3a221939a19f
d4061c6cc0e2
55f2f2b4c462
55f2f2b4c462
5e7c69707f08
d1addaf7a91e
d1addaf7a91e
55f2f2b4c462
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
34f2da0a2969
34f2da0a2969
d1addaf7a91e
34f2da0a2969
d1addaf7a91e
140f2811fc6f
d1addaf7a91e
93e055489e68
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
63bed817308c
d1addaf7a91e
63bed817308c
f4c712ab3e40
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
55f2f2b4c462
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
231442b0b6aa
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
55f2f2b4c462
d1addaf7a91e
d1addaf7a91e
ba5fee3879c8
d1addaf7a91e
231442b0b6aa
d1addaf7a91e
552800808386
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
f4c712ab3e40
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
d1addaf7a91e
34f2da0a2969
d1addaf7a91e
34f2da0a2969
d1addaf7a91e
b5e399286ce5
b5e399286ce5
d1addaf7a91e
d1addaf7a91e
34f2da0a2969
34f2da0a2969
34f2da0a2969
34f2da0a2969
34f2da0a2969
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
b5e399286ce5
5be4d7d6ac05
b5e399286ce5
b5e399286ce5
edb24bc0f71a
b5e399286ce5
b5e399286ce5
edb24bc0f71a
b5e399286ce5
d1addaf7a91e
87285c5007fb
edb24bc0f71a
5be4d7d6ac05
87285c5007fb
f4c712ab3e40
d1addaf7a91e
d1addaf7a91e
b5e399286ce5
d1addaf7a91e
b5e399286ce5
# -*- coding: utf-8 -*-
# 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/>.
"""
kallithea.model.comment
~~~~~~~~~~~~~~~~~~~~~~~

comments model for Kallithea

This file was forked by the Kallithea project in July 2014.
Original author and date, and relevant copyright and licensing information is below:
:created_on: Nov 11, 2011
:author: marcink
:copyright: (c) 2013 RhodeCode GmbH, and others.
:license: GPLv3, see LICENSE.md for more details.
"""

import logging

from pylons.i18n.translation import _
from collections import defaultdict

from kallithea.lib.utils2 import extract_mentioned_users, safe_unicode
from kallithea.lib import helpers as h
from kallithea.model import BaseModel
from kallithea.model.db import ChangesetComment, User, \
    Notification, PullRequest
from kallithea.model.notification import NotificationModel
from kallithea.model.meta import Session

log = logging.getLogger(__name__)


class ChangesetCommentsModel(BaseModel):

    cls = ChangesetComment

    def _get_notification_data(self, repo, comment, user, comment_text,
                               line_no=None, revision=None, pull_request=None,
                               status_change=None, closing_pr=False):
        """
        :returns: tuple (subj,body,recipients,notification_type,email_kwargs)
        """
        # make notification
        body = comment_text  # text of the comment
        line = ''
        if line_no:
            line = _('on line %s') % line_no

        #changeset
        if revision:
            notification_type = Notification.TYPE_CHANGESET_COMMENT
            cs = repo.scm_instance.get_changeset(revision)
            desc = cs.short_id

            threading = ['%s-rev-%s@%s' % (repo.repo_name, revision, h.canonical_hostname())]
            if line_no: # TODO: url to file _and_ line number
                threading.append('%s-rev-%s-line-%s@%s' % (repo.repo_name, revision, line_no,
                                                           h.canonical_hostname()))
            comment_url = h.canonical_url('changeset_home',
                repo_name=repo.repo_name,
                revision=revision,
                anchor='comment-%s' % comment.comment_id)
            subj = safe_unicode(
                h.link_to('Re changeset: %(desc)s %(line)s' % \
                          {'desc': desc, 'line': line},
                          comment_url)
            )
            # get the current participants of this changeset
            recipients = ChangesetComment.get_users(revision=revision)
            # add changeset author if it's known locally
            cs_author = User.get_from_cs_author(cs.author)
            if not cs_author:
                #use repo owner if we cannot extract the author correctly
                # FIXME: just use committer name even if not a user
                cs_author = repo.user
            recipients += [cs_author]
            email_kwargs = {
                'status_change': status_change,
                'cs_comment_user': user.full_name_and_username,
                'cs_target_repo': h.canonical_url('summary_home', repo_name=repo.repo_name),
                'cs_comment_url': comment_url,
                'raw_id': revision,
                'message': cs.message,
                'message_short': h.shorter(cs.message, 50, firstline=True),
                'cs_author': cs_author,
                'repo_name': repo.repo_name,
                'short_id': h.short_id(revision),
                'branch': cs.branch,
                'comment_username': user.username,
                'threading': threading,
            }
        #pull request
        elif pull_request:
            notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
            desc = comment.pull_request.title
            _org_ref_type, org_ref_name, _org_rev = comment.pull_request.org_ref.split(':')
            _other_ref_type, other_ref_name, _other_rev = comment.pull_request.other_ref.split(':')
            threading = ['%s-pr-%s@%s' % (pull_request.other_repo.repo_name,
                                          pull_request.pull_request_id,
                                          h.canonical_hostname())]
            if line_no: # TODO: url to file _and_ line number
                threading.append('%s-pr-%s-line-%s@%s' % (pull_request.other_repo.repo_name,
                                                          pull_request.pull_request_id, line_no,
                                                          h.canonical_hostname()))
            comment_url = pull_request.url(canonical=True,
                anchor='comment-%s' % comment.comment_id)
            subj = safe_unicode(
                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' % \
                          {'desc': desc,
                           'pr_nice_id': comment.pull_request.nice_id(),
                           'line': line},
                          comment_url)
            )
            # get the current participants of this pull request
            recipients = ChangesetComment.get_users(pull_request_id=
                                                pull_request.pull_request_id)
            # add pull request author
            recipients += [pull_request.owner]

            # add the reviewers to notification
            recipients += pull_request.get_reviewer_users()

            #set some variables for email notification
            email_kwargs = {
                'pr_title': pull_request.title,
                'pr_title_short': h.shorter(pull_request.title, 50),
                'pr_nice_id': pull_request.nice_id(),
                'status_change': status_change,
                'closing_pr': closing_pr,
                'pr_comment_url': comment_url,
                'pr_comment_user': user.full_name_and_username,
                'pr_target_repo': h.canonical_url('summary_home',
                                   repo_name=pull_request.other_repo.repo_name),
                'pr_target_branch': other_ref_name,
                'pr_source_repo': h.canonical_url('summary_home',
                                   repo_name=pull_request.org_repo.repo_name),
                'pr_source_branch': org_ref_name,
                'pr_owner': pull_request.owner,
                'pr_owner_username': pull_request.owner.username,
                'repo_name': pull_request.other_repo.repo_name,
                'comment_username': user.username,
                'threading': threading,
            }

        return subj, body, recipients, notification_type, email_kwargs

    def create(self, text, repo, user, revision=None, pull_request=None,
               f_path=None, line_no=None, status_change=None, closing_pr=False,
               send_email=True):
        """
        Creates a new comment for either a changeset or a pull request.
        status_change and closing_pr is only for the optional email.

        Returns the created comment.
        """
        if not status_change and not text:
            log.warning('Missing text for comment, skipping...')
            return None

        repo = self._get_repo(repo)
        user = self._get_user(user)
        comment = ChangesetComment()
        comment.repo = repo
        comment.author = user
        comment.text = text
        comment.f_path = f_path
        comment.line_no = line_no

        if revision is not None:
            comment.revision = revision
        elif pull_request is not None:
            pull_request = self._get_instance(PullRequest, pull_request)
            comment.pull_request = pull_request
        else:
            raise Exception('Please specify revision or pull_request_id')

        Session().add(comment)
        Session().flush()

        if send_email:
            (subj, body, recipients, notification_type,
             email_kwargs) = self._get_notification_data(
                                repo, comment, user,
                                comment_text=text,
                                line_no=line_no,
                                revision=revision,
                                pull_request=pull_request,
                                status_change=status_change,
                                closing_pr=closing_pr)
            email_kwargs['is_mention'] = False
            # create notification objects, and emails
            NotificationModel().create(
                created_by=user, subject=subj, body=body,
                recipients=recipients, type_=notification_type,
                email_kwargs=email_kwargs,
            )

            mention_recipients = extract_mentioned_users(body).difference(recipients)
            if mention_recipients:
                email_kwargs['is_mention'] = True
                subj = _('[Mention]') + ' ' + subj
                # FIXME: this subject is wrong and unused!
                NotificationModel().create(
                    created_by=user, subject=subj, body=body,
                    recipients=mention_recipients,
                    type_=notification_type,
                    email_kwargs=email_kwargs
                )

        return comment

    def delete(self, comment):
        comment = self._get_instance(ChangesetComment, comment)
        Session().delete(comment)

        return comment

    def get_comments(self, repo_id, revision=None, pull_request=None):
        """
        Gets general comments for either revision or pull_request.

        Returns a list, ordered by creation date.
        """
        return self._get_comments(repo_id, revision=revision, pull_request=pull_request,
                                  inline=False)

    def get_inline_comments(self, repo_id, revision=None, pull_request=None):
        """
        Gets inline comments for either revision or pull_request.

        Returns a list of tuples with file path and list of comments per line number.
        """
        comments = self._get_comments(repo_id, revision=revision, pull_request=pull_request,
                                      inline=True)

        paths = defaultdict(lambda: defaultdict(list))
        for co in comments:
            paths[co.f_path][co.line_no].append(co)
        return paths.items()

    def _get_comments(self, repo_id, revision=None, pull_request=None, inline=False):
        """
        Gets comments for either revision or pull_request_id, either inline or general.
        """
        q = Session().query(ChangesetComment)

        if inline:
            q = q.filter(ChangesetComment.line_no != None) \
                .filter(ChangesetComment.f_path != None)
        else:
            q = q.filter(ChangesetComment.line_no == None) \
                .filter(ChangesetComment.f_path == None)

        if revision is not None:
            q = q.filter(ChangesetComment.revision == revision) \
                .filter(ChangesetComment.repo_id == repo_id)
        elif pull_request is not None:
            pull_request = self._get_instance(PullRequest, pull_request)
            q = q.filter(ChangesetComment.pull_request == pull_request)
        else:
            raise Exception('Please specify either revision or pull_request')

        return q.order_by(ChangesetComment.created_on).all()