##// END OF EJS Templates
code garden
marcink -
r2082:0e27da01 beta
parent child Browse files
Show More
@@ -1,148 +1,148 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib import extract_mentioned_users
32 from rhodecode.lib import extract_mentioned_users
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
36 from rhodecode.model.notification import NotificationModel
36 from rhodecode.model.notification import NotificationModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class ChangesetCommentsModel(BaseModel):
41 class ChangesetCommentsModel(BaseModel):
42
42
43 def __get_changeset_comment(self, changeset_comment):
43 def __get_changeset_comment(self, changeset_comment):
44 return self._get_instance(ChangesetComment, changeset_comment)
44 return self._get_instance(ChangesetComment, changeset_comment)
45
45
46 def _extract_mentions(self, s):
46 def _extract_mentions(self, s):
47 user_objects = []
47 user_objects = []
48 for username in extract_mentioned_users(s):
48 for username in extract_mentioned_users(s):
49 user_obj = User.get_by_username(username, case_insensitive=True)
49 user_obj = User.get_by_username(username, case_insensitive=True)
50 if user_obj:
50 if user_obj:
51 user_objects.append(user_obj)
51 user_objects.append(user_obj)
52 return user_objects
52 return user_objects
53
53
54 def create(self, text, repo_id, user_id, revision, f_path=None,
54 def create(self, text, repo_id, user_id, revision, f_path=None,
55 line_no=None):
55 line_no=None):
56 """
56 """
57 Creates new comment for changeset
57 Creates new comment for changeset
58
58
59 :param text:
59 :param text:
60 :param repo_id:
60 :param repo_id:
61 :param user_id:
61 :param user_id:
62 :param revision:
62 :param revision:
63 :param f_path:
63 :param f_path:
64 :param line_no:
64 :param line_no:
65 """
65 """
66 if text:
66 if text:
67 repo = Repository.get(repo_id)
67 repo = Repository.get(repo_id)
68 cs = repo.scm_instance.get_changeset(revision)
68 cs = repo.scm_instance.get_changeset(revision)
69 desc = cs.message
69 desc = cs.message
70 author = cs.author_email
70 author_email = cs.author_email
71 comment = ChangesetComment()
71 comment = ChangesetComment()
72 comment.repo = repo
72 comment.repo = repo
73 comment.user_id = user_id
73 comment.user_id = user_id
74 comment.revision = revision
74 comment.revision = revision
75 comment.text = text
75 comment.text = text
76 comment.f_path = f_path
76 comment.f_path = f_path
77 comment.line_no = line_no
77 comment.line_no = line_no
78
78
79 self.sa.add(comment)
79 self.sa.add(comment)
80 self.sa.flush()
80 self.sa.flush()
81
81
82 # make notification
82 # make notification
83 line = ''
83 line = ''
84 if line_no:
84 if line_no:
85 line = _('on line %s') % line_no
85 line = _('on line %s') % line_no
86 subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
86 subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
87 {'commit_desc': desc, 'line': line},
87 {'commit_desc': desc, 'line': line},
88 h.url('changeset_home', repo_name=repo.repo_name,
88 h.url('changeset_home', repo_name=repo.repo_name,
89 revision=revision,
89 revision=revision,
90 anchor='comment-%s' % comment.comment_id,
90 anchor='comment-%s' % comment.comment_id,
91 qualified=True,
91 qualified=True,
92 )
92 )
93 )
93 )
94 body = text
94 body = text
95
95
96 # get the current participants of this changeset
96 # get the current participants of this changeset
97 recipients = ChangesetComment.get_users(revision=revision)
97 recipients = ChangesetComment.get_users(revision=revision)
98
98
99 # add changeset author
99 # add changeset author if it's in rhodecode system
100 recipients += [User.get_by_email(author)]
100 recipients += [User.get_by_email(author_email)]
101
101
102 NotificationModel().create(
102 NotificationModel().create(
103 created_by=user_id, subject=subj, body=body,
103 created_by=user_id, subject=subj, body=body,
104 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
104 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
105 )
105 )
106
106
107 mention_recipients = set(self._extract_mentions(body))\
107 mention_recipients = set(self._extract_mentions(body))\
108 .difference(recipients)
108 .difference(recipients)
109 if mention_recipients:
109 if mention_recipients:
110 subj = _('[Mention]') + ' ' + subj
110 subj = _('[Mention]') + ' ' + subj
111 NotificationModel().create(
111 NotificationModel().create(
112 created_by=user_id, subject=subj, body=body,
112 created_by=user_id, subject=subj, body=body,
113 recipients=mention_recipients,
113 recipients=mention_recipients,
114 type_=Notification.TYPE_CHANGESET_COMMENT
114 type_=Notification.TYPE_CHANGESET_COMMENT
115 )
115 )
116
116
117 return comment
117 return comment
118
118
119 def delete(self, comment):
119 def delete(self, comment):
120 """
120 """
121 Deletes given comment
121 Deletes given comment
122
122
123 :param comment_id:
123 :param comment_id:
124 """
124 """
125 comment = self.__get_changeset_comment(comment)
125 comment = self.__get_changeset_comment(comment)
126 self.sa.delete(comment)
126 self.sa.delete(comment)
127
127
128 return comment
128 return comment
129
129
130 def get_comments(self, repo_id, revision):
130 def get_comments(self, repo_id, revision):
131 return ChangesetComment.query()\
131 return ChangesetComment.query()\
132 .filter(ChangesetComment.repo_id == repo_id)\
132 .filter(ChangesetComment.repo_id == repo_id)\
133 .filter(ChangesetComment.revision == revision)\
133 .filter(ChangesetComment.revision == revision)\
134 .filter(ChangesetComment.line_no == None)\
134 .filter(ChangesetComment.line_no == None)\
135 .filter(ChangesetComment.f_path == None).all()
135 .filter(ChangesetComment.f_path == None).all()
136
136
137 def get_inline_comments(self, repo_id, revision):
137 def get_inline_comments(self, repo_id, revision):
138 comments = self.sa.query(ChangesetComment)\
138 comments = self.sa.query(ChangesetComment)\
139 .filter(ChangesetComment.repo_id == repo_id)\
139 .filter(ChangesetComment.repo_id == repo_id)\
140 .filter(ChangesetComment.revision == revision)\
140 .filter(ChangesetComment.revision == revision)\
141 .filter(ChangesetComment.line_no != None)\
141 .filter(ChangesetComment.line_no != None)\
142 .filter(ChangesetComment.f_path != None).all()
142 .filter(ChangesetComment.f_path != None).all()
143
143
144 paths = defaultdict(lambda: defaultdict(list))
144 paths = defaultdict(lambda: defaultdict(list))
145
145
146 for co in comments:
146 for co in comments:
147 paths[co.f_path][co.line_no].append(co)
147 paths[co.f_path][co.line_no].append(co)
148 return paths.items()
148 return paths.items()
@@ -1,222 +1,225 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import datetime
30 import datetime
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model import BaseModel
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Notification, User, UserNotification
37 from rhodecode.model.db import Notification, User, UserNotification
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class NotificationModel(BaseModel):
42 class NotificationModel(BaseModel):
43
43
44 def __get_user(self, user):
44 def __get_user(self, user):
45 return self._get_instance(User, user, callback=User.get_by_username)
45 return self._get_instance(User, user, callback=User.get_by_username)
46
46
47 def __get_notification(self, notification):
47 def __get_notification(self, notification):
48 if isinstance(notification, Notification):
48 if isinstance(notification, Notification):
49 return notification
49 return notification
50 elif isinstance(notification, int):
50 elif isinstance(notification, int):
51 return Notification.get(notification)
51 return Notification.get(notification)
52 else:
52 else:
53 if notification:
53 if notification:
54 raise Exception('notification must be int or Instance'
54 raise Exception('notification must be int or Instance'
55 ' of Notification got %s' % type(notification))
55 ' of Notification got %s' % type(notification))
56
56
57 def create(self, created_by, subject, body, recipients=None,
57 def create(self, created_by, subject, body, recipients=None,
58 type_=Notification.TYPE_MESSAGE, with_email=True,
58 type_=Notification.TYPE_MESSAGE, with_email=True,
59 email_kwargs={}):
59 email_kwargs={}):
60 """
60 """
61
61
62 Creates notification of given type
62 Creates notification of given type
63
63
64 :param created_by: int, str or User instance. User who created this
64 :param created_by: int, str or User instance. User who created this
65 notification
65 notification
66 :param subject:
66 :param subject:
67 :param body:
67 :param body:
68 :param recipients: list of int, str or User objects, when None
68 :param recipients: list of int, str or User objects, when None
69 is given send to all admins
69 is given send to all admins
70 :param type_: type of notification
70 :param type_: type of notification
71 :param with_email: send email with this notification
71 :param with_email: send email with this notification
72 :param email_kwargs: additional dict to pass as args to email template
72 :param email_kwargs: additional dict to pass as args to email template
73 """
73 """
74 from rhodecode.lib.celerylib import tasks, run_task
74 from rhodecode.lib.celerylib import tasks, run_task
75
75
76 if recipients and not getattr(recipients, '__iter__', False):
76 if recipients and not getattr(recipients, '__iter__', False):
77 raise Exception('recipients must be a list of iterable')
77 raise Exception('recipients must be a list of iterable')
78
78
79 created_by_obj = self.__get_user(created_by)
79 created_by_obj = self.__get_user(created_by)
80
80
81 if recipients:
81 if recipients:
82 recipients_objs = []
82 recipients_objs = []
83 for u in recipients:
83 for u in recipients:
84 obj = self.__get_user(u)
84 obj = self.__get_user(u)
85 if obj:
85 if obj:
86 recipients_objs.append(obj)
86 recipients_objs.append(obj)
87 recipients_objs = set(recipients_objs)
87 recipients_objs = set(recipients_objs)
88 log.debug('sending notifications %s to %s' % (
88 log.debug('sending notifications %s to %s' % (
89 type_, recipients_objs)
89 type_, recipients_objs)
90 )
90 )
91 else:
91 else:
92 # empty recipients means to all admins
92 # empty recipients means to all admins
93 recipients_objs = User.query().filter(User.admin == True).all()
93 recipients_objs = User.query().filter(User.admin == True).all()
94 log.debug('sending notifications %s to admins: %s' % (
94 log.debug('sending notifications %s to admins: %s' % (
95 type_, recipients_objs)
95 type_, recipients_objs)
96 )
96 )
97 notif = Notification.create(
97 notif = Notification.create(
98 created_by=created_by_obj, subject=subject,
98 created_by=created_by_obj, subject=subject,
99 body=body, recipients=recipients_objs, type_=type_
99 body=body, recipients=recipients_objs, type_=type_
100 )
100 )
101
101
102 if with_email is False:
102 if with_email is False:
103 return notif
103 return notif
104
104
105 # send email with notification
105 # send email with notification
106 for rec in recipients_objs:
106 for rec in recipients_objs:
107 email_subject = NotificationModel().make_description(notif, False)
107 email_subject = NotificationModel().make_description(notif, False)
108 type_ = type_
108 type_ = type_
109 email_body = body
109 email_body = body
110 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
110 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
111 kwargs.update(email_kwargs)
111 kwargs.update(email_kwargs)
112 email_body_html = EmailNotificationModel()\
112 email_body_html = EmailNotificationModel()\
113 .get_email_tmpl(type_, **kwargs)
113 .get_email_tmpl(type_, **kwargs)
114 run_task(tasks.send_email, rec.email, email_subject, email_body,
114 run_task(tasks.send_email, rec.email, email_subject, email_body,
115 email_body_html)
115 email_body_html)
116
116
117 return notif
117 return notif
118
118
119 def delete(self, user, notification):
119 def delete(self, user, notification):
120 # we don't want to remove actual notification just the assignment
120 # we don't want to remove actual notification just the assignment
121 try:
121 try:
122 notification = self.__get_notification(notification)
122 notification = self.__get_notification(notification)
123 user = self.__get_user(user)
123 user = self.__get_user(user)
124 if notification and user:
124 if notification and user:
125 obj = UserNotification.query()\
125 obj = UserNotification.query()\
126 .filter(UserNotification.user == user)\
126 .filter(UserNotification.user == user)\
127 .filter(UserNotification.notification
127 .filter(UserNotification.notification
128 == notification)\
128 == notification)\
129 .one()
129 .one()
130 self.sa.delete(obj)
130 self.sa.delete(obj)
131 return True
131 return True
132 except Exception:
132 except Exception:
133 log.error(traceback.format_exc())
133 log.error(traceback.format_exc())
134 raise
134 raise
135
135
136 def get_for_user(self, user):
136 def get_for_user(self, user):
137 user = self.__get_user(user)
137 user = self.__get_user(user)
138 return user.notifications
138 return user.notifications
139
139
140 def mark_all_read_for_user(self, user):
140 def mark_all_read_for_user(self, user):
141 user = self.__get_user(user)
141 user = self.__get_user(user)
142 UserNotification.query()\
142 UserNotification.query()\
143 .filter(UserNotification.read==False)\
143 .filter(UserNotification.read==False)\
144 .update({'read': True})
144 .update({'read': True})
145
145
146 def get_unread_cnt_for_user(self, user):
146 def get_unread_cnt_for_user(self, user):
147 user = self.__get_user(user)
147 user = self.__get_user(user)
148 return UserNotification.query()\
148 return UserNotification.query()\
149 .filter(UserNotification.read == False)\
149 .filter(UserNotification.read == False)\
150 .filter(UserNotification.user == user).count()
150 .filter(UserNotification.user == user).count()
151
151
152 def get_unread_for_user(self, user):
152 def get_unread_for_user(self, user):
153 user = self.__get_user(user)
153 user = self.__get_user(user)
154 return [x.notification for x in UserNotification.query()\
154 return [x.notification for x in UserNotification.query()\
155 .filter(UserNotification.read == False)\
155 .filter(UserNotification.read == False)\
156 .filter(UserNotification.user == user).all()]
156 .filter(UserNotification.user == user).all()]
157
157
158 def get_user_notification(self, user, notification):
158 def get_user_notification(self, user, notification):
159 user = self.__get_user(user)
159 user = self.__get_user(user)
160 notification = self.__get_notification(notification)
160 notification = self.__get_notification(notification)
161
161
162 return UserNotification.query()\
162 return UserNotification.query()\
163 .filter(UserNotification.notification == notification)\
163 .filter(UserNotification.notification == notification)\
164 .filter(UserNotification.user == user).scalar()
164 .filter(UserNotification.user == user).scalar()
165
165
166 def make_description(self, notification, show_age=True):
166 def make_description(self, notification, show_age=True):
167 """
167 """
168 Creates a human readable description based on properties
168 Creates a human readable description based on properties
169 of notification object
169 of notification object
170 """
170 """
171
171
172 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
172 _map = {
173 notification.TYPE_MESSAGE:_('sent message'),
173 notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
174 notification.TYPE_MENTION:_('mentioned you'),
174 notification.TYPE_MESSAGE: _('sent message'),
175 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
175 notification.TYPE_MENTION: _('mentioned you'),
176 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
177 }
176
178
177 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
179 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
178
180
179 tmpl = "%(user)s %(action)s %(when)s"
181 tmpl = "%(user)s %(action)s %(when)s"
180 if show_age:
182 if show_age:
181 when = h.age(notification.created_on)
183 when = h.age(notification.created_on)
182 else:
184 else:
183 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
185 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
184 when = DTF(notification.created_on)
186 when = DTF(notification.created_on)
185 data = dict(user=notification.created_by_user.username,
187 data = dict(
186 action=_map[notification.type_],
188 user=notification.created_by_user.username,
187 when=when)
189 action=_map[notification.type_], when=when,
190 )
188 return tmpl % data
191 return tmpl % data
189
192
190
193
191 class EmailNotificationModel(BaseModel):
194 class EmailNotificationModel(BaseModel):
192
195
193 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
196 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
194 TYPE_PASSWORD_RESET = 'passoword_link'
197 TYPE_PASSWORD_RESET = 'passoword_link'
195 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
198 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
196 TYPE_DEFAULT = 'default'
199 TYPE_DEFAULT = 'default'
197
200
198 def __init__(self):
201 def __init__(self):
199 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
202 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
200 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
203 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
201
204
202 self.email_types = {
205 self.email_types = {
203 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
206 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
204 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
207 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
205 self.TYPE_REGISTRATION:'email_templates/registration.html',
208 self.TYPE_REGISTRATION: 'email_templates/registration.html',
206 self.TYPE_DEFAULT:'email_templates/default.html'
209 self.TYPE_DEFAULT: 'email_templates/default.html'
207 }
210 }
208
211
209 def get_email_tmpl(self, type_, **kwargs):
212 def get_email_tmpl(self, type_, **kwargs):
210 """
213 """
211 return generated template for email based on given type
214 return generated template for email based on given type
212
215
213 :param type_:
216 :param type_:
214 """
217 """
215
218
216 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
219 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
217 email_template = self._tmpl_lookup.get_template(base)
220 email_template = self._tmpl_lookup.get_template(base)
218 # translator inject
221 # translator inject
219 _kwargs = {'_':_}
222 _kwargs = {'_': _}
220 _kwargs.update(kwargs)
223 _kwargs.update(kwargs)
221 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
224 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
222 return email_template.render(**_kwargs)
225 return email_template.render(**_kwargs)
General Comments 0
You need to be logged in to leave comments. Login now