##// END OF EJS Templates
code garden
marcink -
r2077:17960433 beta
parent child Browse files
Show More
@@ -1,143 +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 = 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
96 # get the current participants of this changeset
95 recipients = ChangesetComment.get_users(revision=revision)
97 recipients = ChangesetComment.get_users(revision=revision)
98
96 # add changeset author
99 # add changeset author
97 recipients += [User.get_by_email(author)]
100 recipients += [User.get_by_email(author)]
98
101
99 NotificationModel().create(created_by=user_id, subject=subj,
102 NotificationModel().create(
100 body=body, recipients=recipients,
103 created_by=user_id, subject=subj, body=body,
101 type_=Notification.TYPE_CHANGESET_COMMENT)
104 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
105 )
102
106
103 mention_recipients = set(self._extract_mentions(body))\
107 mention_recipients = set(self._extract_mentions(body))\
104 .difference(recipients)
108 .difference(recipients)
105 if mention_recipients:
109 if mention_recipients:
106 subj = _('[Mention]') + ' ' + subj
110 subj = _('[Mention]') + ' ' + subj
107 NotificationModel().create(created_by=user_id, subject=subj,
111 NotificationModel().create(
108 body=body,
112 created_by=user_id, subject=subj, body=body,
109 recipients=mention_recipients,
113 recipients=mention_recipients,
110 type_=Notification.TYPE_CHANGESET_COMMENT)
114 type_=Notification.TYPE_CHANGESET_COMMENT
115 )
111
116
112 return comment
117 return comment
113
118
114 def delete(self, comment):
119 def delete(self, comment):
115 """
120 """
116 Deletes given comment
121 Deletes given comment
117
122
118 :param comment_id:
123 :param comment_id:
119 """
124 """
120 comment = self.__get_changeset_comment(comment)
125 comment = self.__get_changeset_comment(comment)
121 self.sa.delete(comment)
126 self.sa.delete(comment)
122
127
123 return comment
128 return comment
124
129
125 def get_comments(self, repo_id, revision):
130 def get_comments(self, repo_id, revision):
126 return ChangesetComment.query()\
131 return ChangesetComment.query()\
127 .filter(ChangesetComment.repo_id == repo_id)\
132 .filter(ChangesetComment.repo_id == repo_id)\
128 .filter(ChangesetComment.revision == revision)\
133 .filter(ChangesetComment.revision == revision)\
129 .filter(ChangesetComment.line_no == None)\
134 .filter(ChangesetComment.line_no == None)\
130 .filter(ChangesetComment.f_path == None).all()
135 .filter(ChangesetComment.f_path == None).all()
131
136
132 def get_inline_comments(self, repo_id, revision):
137 def get_inline_comments(self, repo_id, revision):
133 comments = self.sa.query(ChangesetComment)\
138 comments = self.sa.query(ChangesetComment)\
134 .filter(ChangesetComment.repo_id == repo_id)\
139 .filter(ChangesetComment.repo_id == repo_id)\
135 .filter(ChangesetComment.revision == revision)\
140 .filter(ChangesetComment.revision == revision)\
136 .filter(ChangesetComment.line_no != None)\
141 .filter(ChangesetComment.line_no != None)\
137 .filter(ChangesetComment.f_path != None).all()
142 .filter(ChangesetComment.f_path != None).all()
138
143
139 paths = defaultdict(lambda: defaultdict(list))
144 paths = defaultdict(lambda: defaultdict(list))
140
145
141 for co in comments:
146 for co in comments:
142 paths[co.f_path][co.line_no].append(co)
147 paths[co.f_path][co.line_no].append(co)
143 return paths.items()
148 return paths.items()
@@ -1,216 +1,222 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' % (
89 type_, recipients_objs)
90 )
88 else:
91 else:
89 # empty recipients means to all admins
92 # empty recipients means to all admins
90 recipients_objs = User.query().filter(User.admin == True).all()
93 recipients_objs = User.query().filter(User.admin == True).all()
91
94 log.debug('sending notifications %s to admins: %s' % (
92 notif = Notification.create(created_by=created_by_obj, subject=subject,
95 type_, recipients_objs)
93 body=body, recipients=recipients_objs,
96 )
94 type_=type_)
97 notif = Notification.create(
98 created_by=created_by_obj, subject=subject,
99 body=body, recipients=recipients_objs, type_=type_
100 )
95
101
96 if with_email is False:
102 if with_email is False:
97 return notif
103 return notif
98
104
99 # send email with notification
105 # send email with notification
100 for rec in recipients_objs:
106 for rec in recipients_objs:
101 email_subject = NotificationModel().make_description(notif, False)
107 email_subject = NotificationModel().make_description(notif, False)
102 type_ = type_
108 type_ = type_
103 email_body = body
109 email_body = body
104 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
110 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
105 kwargs.update(email_kwargs)
111 kwargs.update(email_kwargs)
106 email_body_html = EmailNotificationModel()\
112 email_body_html = EmailNotificationModel()\
107 .get_email_tmpl(type_, **kwargs)
113 .get_email_tmpl(type_, **kwargs)
108 run_task(tasks.send_email, rec.email, email_subject, email_body,
114 run_task(tasks.send_email, rec.email, email_subject, email_body,
109 email_body_html)
115 email_body_html)
110
116
111 return notif
117 return notif
112
118
113 def delete(self, user, notification):
119 def delete(self, user, notification):
114 # we don't want to remove actual notification just the assignment
120 # we don't want to remove actual notification just the assignment
115 try:
121 try:
116 notification = self.__get_notification(notification)
122 notification = self.__get_notification(notification)
117 user = self.__get_user(user)
123 user = self.__get_user(user)
118 if notification and user:
124 if notification and user:
119 obj = UserNotification.query()\
125 obj = UserNotification.query()\
120 .filter(UserNotification.user == user)\
126 .filter(UserNotification.user == user)\
121 .filter(UserNotification.notification
127 .filter(UserNotification.notification
122 == notification)\
128 == notification)\
123 .one()
129 .one()
124 self.sa.delete(obj)
130 self.sa.delete(obj)
125 return True
131 return True
126 except Exception:
132 except Exception:
127 log.error(traceback.format_exc())
133 log.error(traceback.format_exc())
128 raise
134 raise
129
135
130 def get_for_user(self, user):
136 def get_for_user(self, user):
131 user = self.__get_user(user)
137 user = self.__get_user(user)
132 return user.notifications
138 return user.notifications
133
139
134 def mark_all_read_for_user(self, user):
140 def mark_all_read_for_user(self, user):
135 user = self.__get_user(user)
141 user = self.__get_user(user)
136 UserNotification.query()\
142 UserNotification.query()\
137 .filter(UserNotification.read==False)\
143 .filter(UserNotification.read==False)\
138 .update({'read': True})
144 .update({'read': True})
139
145
140 def get_unread_cnt_for_user(self, user):
146 def get_unread_cnt_for_user(self, user):
141 user = self.__get_user(user)
147 user = self.__get_user(user)
142 return UserNotification.query()\
148 return UserNotification.query()\
143 .filter(UserNotification.read == False)\
149 .filter(UserNotification.read == False)\
144 .filter(UserNotification.user == user).count()
150 .filter(UserNotification.user == user).count()
145
151
146 def get_unread_for_user(self, user):
152 def get_unread_for_user(self, user):
147 user = self.__get_user(user)
153 user = self.__get_user(user)
148 return [x.notification for x in UserNotification.query()\
154 return [x.notification for x in UserNotification.query()\
149 .filter(UserNotification.read == False)\
155 .filter(UserNotification.read == False)\
150 .filter(UserNotification.user == user).all()]
156 .filter(UserNotification.user == user).all()]
151
157
152 def get_user_notification(self, user, notification):
158 def get_user_notification(self, user, notification):
153 user = self.__get_user(user)
159 user = self.__get_user(user)
154 notification = self.__get_notification(notification)
160 notification = self.__get_notification(notification)
155
161
156 return UserNotification.query()\
162 return UserNotification.query()\
157 .filter(UserNotification.notification == notification)\
163 .filter(UserNotification.notification == notification)\
158 .filter(UserNotification.user == user).scalar()
164 .filter(UserNotification.user == user).scalar()
159
165
160 def make_description(self, notification, show_age=True):
166 def make_description(self, notification, show_age=True):
161 """
167 """
162 Creates a human readable description based on properties
168 Creates a human readable description based on properties
163 of notification object
169 of notification object
164 """
170 """
165
171
166 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
172 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
167 notification.TYPE_MESSAGE:_('sent message'),
173 notification.TYPE_MESSAGE:_('sent message'),
168 notification.TYPE_MENTION:_('mentioned you'),
174 notification.TYPE_MENTION:_('mentioned you'),
169 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
175 notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
170
176
171 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
177 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
172
178
173 tmpl = "%(user)s %(action)s %(when)s"
179 tmpl = "%(user)s %(action)s %(when)s"
174 if show_age:
180 if show_age:
175 when = h.age(notification.created_on)
181 when = h.age(notification.created_on)
176 else:
182 else:
177 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
183 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
178 when = DTF(notification.created_on)
184 when = DTF(notification.created_on)
179 data = dict(user=notification.created_by_user.username,
185 data = dict(user=notification.created_by_user.username,
180 action=_map[notification.type_],
186 action=_map[notification.type_],
181 when=when)
187 when=when)
182 return tmpl % data
188 return tmpl % data
183
189
184
190
185 class EmailNotificationModel(BaseModel):
191 class EmailNotificationModel(BaseModel):
186
192
187 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
193 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
188 TYPE_PASSWORD_RESET = 'passoword_link'
194 TYPE_PASSWORD_RESET = 'passoword_link'
189 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
195 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
190 TYPE_DEFAULT = 'default'
196 TYPE_DEFAULT = 'default'
191
197
192 def __init__(self):
198 def __init__(self):
193 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
199 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
194 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
200 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
195
201
196 self.email_types = {
202 self.email_types = {
197 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
203 self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
198 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
204 self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
199 self.TYPE_REGISTRATION:'email_templates/registration.html',
205 self.TYPE_REGISTRATION:'email_templates/registration.html',
200 self.TYPE_DEFAULT:'email_templates/default.html'
206 self.TYPE_DEFAULT:'email_templates/default.html'
201 }
207 }
202
208
203 def get_email_tmpl(self, type_, **kwargs):
209 def get_email_tmpl(self, type_, **kwargs):
204 """
210 """
205 return generated template for email based on given type
211 return generated template for email based on given type
206
212
207 :param type_:
213 :param type_:
208 """
214 """
209
215
210 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
216 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
211 email_template = self._tmpl_lookup.get_template(base)
217 email_template = self._tmpl_lookup.get_template(base)
212 # translator inject
218 # translator inject
213 _kwargs = {'_':_}
219 _kwargs = {'_':_}
214 _kwargs.update(kwargs)
220 _kwargs.update(kwargs)
215 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
221 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
216 return email_template.render(**_kwargs)
222 return email_template.render(**_kwargs)
General Comments 0
You need to be logged in to leave comments. Login now