##// END OF EJS Templates
Added filtering on inbox by comments
marcink -
r2503:d04243e9 beta
parent child Browse files
Show More
@@ -1,148 +1,150 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.notifications
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 notifications controller for RhodeCode
7 7
8 8 :created_on: Nov 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons import request
30 30 from pylons import tmpl_context as c, url
31 31 from pylons.controllers.util import redirect
32 32
33 33 from webhelpers.paginate import Page
34 34
35 35 from rhodecode.lib.base import BaseController, render
36 36 from rhodecode.model.db import Notification
37 37
38 38 from rhodecode.model.notification import NotificationModel
39 39 from rhodecode.lib.auth import LoginRequired, NotAnonymous
40 40 from rhodecode.lib import helpers as h
41 41 from rhodecode.model.meta import Session
42 42
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class NotificationsController(BaseController):
48 48 """REST Controller styled on the Atom Publishing Protocol"""
49 49 # To properly map this controller, ensure your config/routing.py
50 50 # file has a resource setup:
51 51 # map.resource('notification', 'notifications', controller='_admin/notifications',
52 52 # path_prefix='/_admin', name_prefix='_admin_')
53 53
54 54 @LoginRequired()
55 55 @NotAnonymous()
56 56 def __before__(self):
57 57 super(NotificationsController, self).__before__()
58 58
59 59 def index(self, format='html'):
60 60 """GET /_admin/notifications: All items in the collection"""
61 61 # url('notifications')
62 62 c.user = self.rhodecode_user
63 63 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
64 filter_=request.GET)
64 filter_=request.GET.getall('type'))
65 65 p = int(request.params.get('page', 1))
66 66 c.notifications = Page(notif, page=p, items_per_page=10)
67 67 c.pull_request_type = Notification.TYPE_PULL_REQUEST
68 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
69 Notification.TYPE_PULL_REQUEST_COMMENT]
68 70 return render('admin/notifications/notifications.html')
69 71
70 72 def mark_all_read(self):
71 73 if request.environ.get('HTTP_X_PARTIAL_XHR'):
72 74 nm = NotificationModel()
73 75 # mark all read
74 76 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
75 filter_=request.GET)
77 filter_=request.GET.getall('type'))
76 78 Session.commit()
77 79 c.user = self.rhodecode_user
78 80 notif = nm.get_for_user(self.rhodecode_user.user_id,
79 filter_=request.GET)
81 filter_=request.GET.getall('type'))
80 82 c.notifications = Page(notif, page=1, items_per_page=10)
81 83 return render('admin/notifications/notifications_data.html')
82 84
83 85 def create(self):
84 86 """POST /_admin/notifications: Create a new item"""
85 87 # url('notifications')
86 88
87 89 def new(self, format='html'):
88 90 """GET /_admin/notifications/new: Form to create a new item"""
89 91 # url('new_notification')
90 92
91 93 def update(self, notification_id):
92 94 """PUT /_admin/notifications/id: Update an existing item"""
93 95 # Forms posted to this method should contain a hidden field:
94 96 # <input type="hidden" name="_method" value="PUT" />
95 97 # Or using helpers:
96 98 # h.form(url('notification', notification_id=ID),
97 99 # method='put')
98 100 # url('notification', notification_id=ID)
99 101
100 102 def delete(self, notification_id):
101 103 """DELETE /_admin/notifications/id: Delete an existing item"""
102 104 # Forms posted to this method should contain a hidden field:
103 105 # <input type="hidden" name="_method" value="DELETE" />
104 106 # Or using helpers:
105 107 # h.form(url('notification', notification_id=ID),
106 108 # method='delete')
107 109 # url('notification', notification_id=ID)
108 110
109 111 try:
110 112 no = Notification.get(notification_id)
111 113 owner = lambda: (no.notifications_to_users.user.user_id
112 114 == c.rhodecode_user.user_id)
113 115 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
114 116 NotificationModel().delete(c.rhodecode_user.user_id, no)
115 117 Session.commit()
116 118 return 'ok'
117 119 except Exception:
118 120 Session.rollback()
119 121 log.error(traceback.format_exc())
120 122 return 'fail'
121 123
122 124 def show(self, notification_id, format='html'):
123 125 """GET /_admin/notifications/id: Show a specific item"""
124 126 # url('notification', notification_id=ID)
125 127 c.user = self.rhodecode_user
126 128 no = Notification.get(notification_id)
127 129
128 130 owner = lambda: (no.notifications_to_users.user.user_id
129 131 == c.user.user_id)
130 132 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
131 133 unotification = NotificationModel()\
132 134 .get_user_notification(c.user.user_id, no)
133 135
134 136 # if this association to user is not valid, we don't want to show
135 137 # this message
136 138 if unotification:
137 139 if unotification.read is False:
138 140 unotification.mark_as_read()
139 141 Session.commit()
140 142 c.notification = no
141 143
142 144 return render('admin/notifications/show_notification.html')
143 145
144 146 return redirect(url('notifications'))
145 147
146 148 def edit(self, notification_id, format='html'):
147 149 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
148 150 # url('edit_notification', notification_id=ID)
@@ -1,257 +1,255 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.notification
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Model for notifications
7 7
8 8
9 9 :created_on: Nov 20, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 import datetime
31 30
32 31 from pylons.i18n.translation import _
33 32
34 33 import rhodecode
35 34 from rhodecode.lib import helpers as h
36 35 from rhodecode.model import BaseModel
37 36 from rhodecode.model.db import Notification, User, UserNotification
38 from sqlalchemy.orm import joinedload
39 37
40 38 log = logging.getLogger(__name__)
41 39
42 40
43 41 class NotificationModel(BaseModel):
44 42
45 43 def __get_notification(self, notification):
46 44 if isinstance(notification, Notification):
47 45 return notification
48 46 elif isinstance(notification, (int, long)):
49 47 return Notification.get(notification)
50 48 else:
51 49 if notification:
52 50 raise Exception('notification must be int, long or Instance'
53 51 ' of Notification got %s' % type(notification))
54 52
55 53 def create(self, created_by, subject, body, recipients=None,
56 54 type_=Notification.TYPE_MESSAGE, with_email=True,
57 55 email_kwargs={}):
58 56 """
59 57
60 58 Creates notification of given type
61 59
62 60 :param created_by: int, str or User instance. User who created this
63 61 notification
64 62 :param subject:
65 63 :param body:
66 64 :param recipients: list of int, str or User objects, when None
67 65 is given send to all admins
68 66 :param type_: type of notification
69 67 :param with_email: send email with this notification
70 68 :param email_kwargs: additional dict to pass as args to email template
71 69 """
72 70 from rhodecode.lib.celerylib import tasks, run_task
73 71
74 72 if recipients and not getattr(recipients, '__iter__', False):
75 73 raise Exception('recipients must be a list of iterable')
76 74
77 75 created_by_obj = self._get_user(created_by)
78 76
79 77 if recipients:
80 78 recipients_objs = []
81 79 for u in recipients:
82 80 obj = self._get_user(u)
83 81 if obj:
84 82 recipients_objs.append(obj)
85 83 recipients_objs = set(recipients_objs)
86 84 log.debug('sending notifications %s to %s' % (
87 85 type_, recipients_objs)
88 86 )
89 87 else:
90 88 # empty recipients means to all admins
91 89 recipients_objs = User.query().filter(User.admin == True).all()
92 90 log.debug('sending notifications %s to admins: %s' % (
93 91 type_, recipients_objs)
94 92 )
95 93 notif = Notification.create(
96 94 created_by=created_by_obj, subject=subject,
97 95 body=body, recipients=recipients_objs, type_=type_
98 96 )
99 97
100 98 if with_email is False:
101 99 return notif
102 100
103 101 #don't send email to person who created this comment
104 102 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
105 103
106 104 # send email with notification to all other participants
107 105 for rec in rec_objs:
108 106 email_subject = NotificationModel().make_description(notif, False)
109 107 type_ = type_
110 108 email_body = body
111 109 ## this is passed into template
112 110 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
113 111 kwargs.update(email_kwargs)
114 112 email_body_html = EmailNotificationModel()\
115 113 .get_email_tmpl(type_, **kwargs)
116 114
117 115 run_task(tasks.send_email, rec.email, email_subject, email_body,
118 116 email_body_html)
119 117
120 118 return notif
121 119
122 120 def delete(self, user, notification):
123 121 # we don't want to remove actual notification just the assignment
124 122 try:
125 123 notification = self.__get_notification(notification)
126 124 user = self._get_user(user)
127 125 if notification and user:
128 126 obj = UserNotification.query()\
129 127 .filter(UserNotification.user == user)\
130 128 .filter(UserNotification.notification
131 129 == notification)\
132 130 .one()
133 131 self.sa.delete(obj)
134 132 return True
135 133 except Exception:
136 134 log.error(traceback.format_exc())
137 135 raise
138 136
139 137 def get_for_user(self, user, filter_=None):
140 138 """
141 139 Get mentions for given user, filter them if filter dict is given
142 140
143 141 :param user:
144 142 :type user:
145 143 :param filter:
146 144 """
147 145 user = self._get_user(user)
148 146
149 147 q = UserNotification.query()\
150 148 .filter(UserNotification.user == user)\
151 149 .join((Notification, UserNotification.notification_id ==
152 150 Notification.notification_id))
153 151
154 152 if filter_:
155 q = q.filter(Notification.type_ == filter_.get('type'))
153 q = q.filter(Notification.type_.in_(filter_))
156 154
157 155 return q.all()
158 156
159 157 def mark_all_read_for_user(self, user, filter_=None):
160 158 user = self._get_user(user)
161 159 q = UserNotification.query()\
162 160 .filter(UserNotification.user == user)\
163 161 .filter(UserNotification.read == False)\
164 162 .join((Notification, UserNotification.notification_id ==
165 163 Notification.notification_id))
166 164 if filter_:
167 q = q.filter(Notification.type_ == filter_.get('type'))
165 q = q.filter(Notification.type_.in_(filter_))
168 166
169 167 # this is a little inefficient but sqlalchemy doesn't support
170 168 # update on joined tables :(
171 169 for obj in q.all():
172 170 obj.read = True
173 171 self.sa.add(obj)
174 172
175 173 def get_unread_cnt_for_user(self, user):
176 174 user = self._get_user(user)
177 175 return UserNotification.query()\
178 176 .filter(UserNotification.read == False)\
179 177 .filter(UserNotification.user == user).count()
180 178
181 179 def get_unread_for_user(self, user):
182 180 user = self._get_user(user)
183 181 return [x.notification for x in UserNotification.query()\
184 182 .filter(UserNotification.read == False)\
185 183 .filter(UserNotification.user == user).all()]
186 184
187 185 def get_user_notification(self, user, notification):
188 186 user = self._get_user(user)
189 187 notification = self.__get_notification(notification)
190 188
191 189 return UserNotification.query()\
192 190 .filter(UserNotification.notification == notification)\
193 191 .filter(UserNotification.user == user).scalar()
194 192
195 193 def make_description(self, notification, show_age=True):
196 194 """
197 195 Creates a human readable description based on properties
198 196 of notification object
199 197 """
200 198 #alias
201 199 _n = notification
202 200 _map = {
203 201 _n.TYPE_CHANGESET_COMMENT: _('commented on commit'),
204 202 _n.TYPE_MESSAGE: _('sent message'),
205 203 _n.TYPE_MENTION: _('mentioned you'),
206 204 _n.TYPE_REGISTRATION: _('registered in RhodeCode'),
207 205 _n.TYPE_PULL_REQUEST: _('opened new pull request'),
208 206 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request')
209 207 }
210 208
211 209 # action == _map string
212 210 tmpl = "%(user)s %(action)s at %(when)s"
213 211 if show_age:
214 212 when = h.age(notification.created_on)
215 213 else:
216 214 when = h.fmt_date(notification.created_on)
217 215
218 216 data = dict(
219 217 user=notification.created_by_user.username,
220 218 action=_map[notification.type_], when=when,
221 219 )
222 220 return tmpl % data
223 221
224 222
225 223 class EmailNotificationModel(BaseModel):
226 224
227 225 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
228 226 TYPE_PASSWORD_RESET = 'passoword_link'
229 227 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
230 228 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
231 229 TYPE_DEFAULT = 'default'
232 230
233 231 def __init__(self):
234 232 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
235 233 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
236 234
237 235 self.email_types = {
238 236 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
239 237 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
240 238 self.TYPE_REGISTRATION: 'email_templates/registration.html',
241 239 self.TYPE_DEFAULT: 'email_templates/default.html'
242 240 }
243 241
244 242 def get_email_tmpl(self, type_, **kwargs):
245 243 """
246 244 return generated template for email based on given type
247 245
248 246 :param type_:
249 247 """
250 248
251 249 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
252 250 email_template = self._tmpl_lookup.get_template(base)
253 251 # translator inject
254 252 _kwargs = {'_': _}
255 253 _kwargs.update(kwargs)
256 254 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
257 255 return email_template.render(**_kwargs)
@@ -1,60 +1,58 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('My Notifications')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${_('My Notifications')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 ##<ul class="links">
22 22 ## <li>
23 23 ## <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
24 24 ## </li>
25 25 ##</ul>
26 26 </div>
27 %if c.notifications:
27
28 28 <div style="padding:14px 18px;text-align: right;float:left">
29 29 <span id='all' class="ui-btn"><a href="${h.url.current()}">${_('All')}</a></span>
30 <span id='pull_request' class="ui-btn"><a href="${h.url.current(type=c.comment_type)}">${_('Comments')}</a></span>
30 31 <span id='pull_request' class="ui-btn"><a href="${h.url.current(type=c.pull_request_type)}">${_('Pull requests')}</a></span>
31 32 </div>
33 %if c.notifications:
32 34 <div style="padding:14px 18px;text-align: right;float:right">
33 35 <span id='mark_all_read' class="ui-btn">${_('Mark all read')}</span>
34 36 </div>
35 37 %endif
36 38 <div id='notification_data'>
37 39 <%include file='notifications_data.html'/>
38 40 </div>
39 41 </div>
40 42 <script type="text/javascript">
41 43 var url_del = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
42 44 YUE.on(YUQ('.delete-notification'),'click',function(e){
43 45 var notification_id = e.currentTarget.id;
44 46 deleteNotification(url_del,notification_id)
45 47 })
46 48 YUE.on('mark_all_read','click',function(e){
47 var url = "${h.url('notifications_mark_all_read', **request.GET)}";
49 var url = "${h.url('notifications_mark_all_read', **request.GET.mixed())}";
48 50 ypjax(url,'notification_data',function(){
49 var notification_counter = YUD.get('notification_counter');
50 if(notification_counter){
51 notification_counter.innerHTML=0;
52 }
53 51 YUE.on(YUQ('.delete-notification'),'click',function(e){
54 52 var notification_id = e.currentTarget.id;
55 53 deleteNotification(url_del,notification_id)
56 54 })
57 55 });
58 56 })
59 57 </script>
60 58 </%def>
General Comments 0
You need to be logged in to leave comments. Login now