notifications: ported views to pyramid
This commit is contained in:
parent
7caba28f0e
commit
580a0d0ea5
15 changed files with 466 additions and 380 deletions
|
|
@ -95,6 +95,28 @@ def includeme(config):
|
|||
name='my_account_pullrequests_data',
|
||||
pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
|
||||
|
||||
# notifications
|
||||
config.add_route(
|
||||
name='notifications_show_all',
|
||||
pattern=ADMIN_PREFIX + '/notifications')
|
||||
|
||||
# notifications
|
||||
config.add_route(
|
||||
name='notifications_mark_all_read',
|
||||
pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
|
||||
|
||||
config.add_route(
|
||||
name='notifications_show',
|
||||
pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
|
||||
|
||||
config.add_route(
|
||||
name='notifications_update',
|
||||
pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
|
||||
|
||||
config.add_route(
|
||||
name='notifications_delete',
|
||||
pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
|
||||
|
||||
# channelstream test
|
||||
config.add_route(
|
||||
name='my_account_notifications_test_channelstream',
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from rhodecode.tests import *
|
||||
from rhodecode.apps._base import ADMIN_PREFIX
|
||||
from rhodecode.tests import (
|
||||
TestController, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
|
||||
TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
|
||||
from rhodecode.tests.fixture import Fixture
|
||||
|
||||
from rhodecode.model.db import Notification, User
|
||||
|
|
@ -31,12 +34,25 @@ from rhodecode.model.meta import Session
|
|||
fixture = Fixture()
|
||||
|
||||
|
||||
class TestNotificationsController(TestController):
|
||||
destroy_users = set()
|
||||
def route_path(name, params=None, **kwargs):
|
||||
import urllib
|
||||
from rhodecode.apps._base import ADMIN_PREFIX
|
||||
|
||||
@classmethod
|
||||
def teardown_class(cls):
|
||||
fixture.destroy_users(cls.destroy_users)
|
||||
base_url = {
|
||||
'notifications_show_all': ADMIN_PREFIX + '/notifications',
|
||||
'notifications_mark_all_read': ADMIN_PREFIX + '/notifications/mark_all_read',
|
||||
'notifications_show': ADMIN_PREFIX + '/notifications/{notification_id}',
|
||||
'notifications_update': ADMIN_PREFIX + '/notifications/{notification_id}/update',
|
||||
'notifications_delete': ADMIN_PREFIX + '/notifications/{notification_id}/delete',
|
||||
|
||||
}[name].format(**kwargs)
|
||||
|
||||
if params:
|
||||
base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
|
||||
return base_url
|
||||
|
||||
|
||||
class TestNotificationsController(TestController):
|
||||
|
||||
def teardown_method(self, method):
|
||||
for n in Notification.query().all():
|
||||
|
|
@ -44,43 +60,61 @@ class TestNotificationsController(TestController):
|
|||
Session().delete(inst)
|
||||
Session().commit()
|
||||
|
||||
def test_index(self):
|
||||
u1 = UserModel().create_or_update(
|
||||
username='u1', password='qweqwe', email='u1@rhodecode.org',
|
||||
firstname='u1', lastname='u1')
|
||||
u1 = u1.user_id
|
||||
self.destroy_users.add('u1')
|
||||
def test_show_all(self, user_util):
|
||||
user = user_util.create_user(password='qweqwe')
|
||||
user_id = user.user_id
|
||||
self.log_user(user.username, 'qweqwe')
|
||||
|
||||
self.log_user('u1', 'qweqwe')
|
||||
|
||||
response = self.app.get(url('notifications'))
|
||||
response = self.app.get(
|
||||
route_path('notifications_show_all', params={'type': 'all'}))
|
||||
response.mustcontain(
|
||||
'<div class="table">No notifications here yet</div>')
|
||||
|
||||
cur_user = self._get_logged_user()
|
||||
notif = NotificationModel().create(
|
||||
created_by=u1, notification_subject=u'test_notification_1',
|
||||
notification_body=u'notification_1', recipients=[cur_user])
|
||||
notification = NotificationModel().create(
|
||||
created_by=user_id, notification_subject=u'test_notification_1',
|
||||
notification_body=u'notification_1', recipients=[user_id])
|
||||
Session().commit()
|
||||
response = self.app.get(url('notifications'))
|
||||
response.mustcontain('id="notification_%s"' % notif.notification_id)
|
||||
notification_id = notification.notification_id
|
||||
|
||||
response = self.app.get(route_path('notifications_show_all',
|
||||
params={'type': 'all'}))
|
||||
response.mustcontain('id="notification_%s"' % notification_id)
|
||||
|
||||
def test_show_unread(self, user_util):
|
||||
user = user_util.create_user(password='qweqwe')
|
||||
user_id = user.user_id
|
||||
self.log_user(user.username, 'qweqwe')
|
||||
|
||||
response = self.app.get(route_path('notifications_show_all'))
|
||||
response.mustcontain(
|
||||
'<div class="table">No notifications here yet</div>')
|
||||
|
||||
notification = NotificationModel().create(
|
||||
created_by=user_id, notification_subject=u'test_notification_1',
|
||||
notification_body=u'notification_1', recipients=[user_id])
|
||||
|
||||
# mark the USER notification as unread
|
||||
user_notification = NotificationModel().get_user_notification(
|
||||
user_id, notification)
|
||||
user_notification.read = False
|
||||
|
||||
Session().commit()
|
||||
notification_id = notification.notification_id
|
||||
|
||||
response = self.app.get(route_path('notifications_show_all'))
|
||||
response.mustcontain('id="notification_%s"' % notification_id)
|
||||
response.mustcontain('<div class="desc unread')
|
||||
|
||||
@pytest.mark.parametrize('user,password', [
|
||||
(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS),
|
||||
(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS),
|
||||
])
|
||||
def test_delete(self, user, password):
|
||||
def test_delete(self, user, password, user_util):
|
||||
self.log_user(user, password)
|
||||
cur_user = self._get_logged_user()
|
||||
|
||||
u1 = UserModel().create_or_update(
|
||||
username='u1', password='qweqwe',
|
||||
email='u1@rhodecode.org', firstname='u1', lastname='u1')
|
||||
u2 = UserModel().create_or_update(
|
||||
username='u2', password='qweqwe', email='u2@rhodecode.org',
|
||||
firstname='u2', lastname='u2')
|
||||
self.destroy_users.add('u1')
|
||||
self.destroy_users.add('u2')
|
||||
u1 = user_util.create_user()
|
||||
u2 = user_util.create_user()
|
||||
|
||||
# make notifications
|
||||
notification = NotificationModel().create(
|
||||
|
|
@ -98,9 +132,10 @@ class TestNotificationsController(TestController):
|
|||
cur_usr_id = cur_user.user_id
|
||||
|
||||
response = self.app.post(
|
||||
url('notification', notification_id=notification.notification_id),
|
||||
params={'_method': 'delete', 'csrf_token': self.csrf_token})
|
||||
assert response.body == 'ok'
|
||||
route_path('notifications_delete',
|
||||
notification_id=notification.notification_id),
|
||||
params={'csrf_token': self.csrf_token})
|
||||
assert response.json == 'ok'
|
||||
|
||||
cur_user = User.get(cur_usr_id)
|
||||
assert cur_user.notifications == []
|
||||
|
|
@ -109,17 +144,11 @@ class TestNotificationsController(TestController):
|
|||
(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS),
|
||||
(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS),
|
||||
])
|
||||
def test_show(self, user, password):
|
||||
def test_show(self, user, password, user_util):
|
||||
self.log_user(user, password)
|
||||
cur_user = self._get_logged_user()
|
||||
u1 = UserModel().create_or_update(username='u1', password='qweqwe',
|
||||
email='u1@rhodecode.org',
|
||||
firstname='u1', lastname='u1')
|
||||
u2 = UserModel().create_or_update(username='u2', password='qweqwe',
|
||||
email='u2@rhodecode.org',
|
||||
firstname='u2', lastname='u2')
|
||||
self.destroy_users.add('u1')
|
||||
self.destroy_users.add('u2')
|
||||
u1 = user_util.create_user()
|
||||
u2 = user_util.create_user()
|
||||
|
||||
subject = u'test'
|
||||
notif_body = u'hi there'
|
||||
|
|
@ -127,8 +156,9 @@ class TestNotificationsController(TestController):
|
|||
created_by=cur_user, notification_subject=subject,
|
||||
notification_body=notif_body, recipients=[cur_user, u1, u2])
|
||||
|
||||
response = self.app.get(url(
|
||||
'notification', notification_id=notification.notification_id))
|
||||
response = self.app.get(
|
||||
route_path('notifications_show',
|
||||
notification_id=notification.notification_id))
|
||||
|
||||
response.mustcontain(subject)
|
||||
response.mustcontain(notif_body)
|
||||
|
|
@ -137,18 +167,11 @@ class TestNotificationsController(TestController):
|
|||
(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS),
|
||||
(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS),
|
||||
])
|
||||
def test_update(self, user, password):
|
||||
def test_update(self, user, password, user_util):
|
||||
self.log_user(user, password)
|
||||
cur_user = self._get_logged_user()
|
||||
|
||||
u1 = UserModel().create_or_update(username='u1', password='qweqwe',
|
||||
email='u1@rhodecode.org',
|
||||
firstname='u1', lastname='u1')
|
||||
u2 = UserModel().create_or_update(username='u2', password='qweqwe',
|
||||
email='u2@rhodecode.org',
|
||||
firstname='u2', lastname='u2')
|
||||
self.destroy_users.add('u1')
|
||||
self.destroy_users.add('u2')
|
||||
u1 = user_util.create_user()
|
||||
u2 = user_util.create_user()
|
||||
|
||||
# make notifications
|
||||
recipients = [cur_user, u1, u2]
|
||||
|
|
@ -164,9 +187,10 @@ class TestNotificationsController(TestController):
|
|||
assert u_obj.notifications[0].read == read
|
||||
|
||||
response = self.app.post(
|
||||
url('notification', notification_id=notification.notification_id),
|
||||
params={'_method': 'put', 'csrf_token': self.csrf_token})
|
||||
assert response.body == 'ok'
|
||||
route_path('notifications_update',
|
||||
notification_id=notification.notification_id),
|
||||
params={'csrf_token': self.csrf_token})
|
||||
assert response.json == 'ok'
|
||||
|
||||
cur_user = self._get_logged_user()
|
||||
assert True == cur_user.notifications[0].read
|
||||
assert True is cur_user.notifications[0].read
|
||||
19
rhodecode/apps/my_account/views/__init__.py
Normal file
19
rhodecode/apps/my_account/views/__init__.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2011-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# 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 Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
198
rhodecode/apps/my_account/views/my_account_notifications.py
Normal file
198
rhodecode/apps/my_account/views/my_account_notifications.py
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2010-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# 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 Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
import logging
|
||||
|
||||
from pyramid.httpexceptions import (
|
||||
HTTPFound, HTTPNotFound, HTTPInternalServerError)
|
||||
from pyramid.view import view_config
|
||||
|
||||
from rhodecode.apps._base import BaseAppView
|
||||
from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
|
||||
|
||||
from rhodecode.lib import helpers as h
|
||||
from rhodecode.lib.helpers import Page
|
||||
from rhodecode.lib.utils2 import safe_int
|
||||
from rhodecode.model.db import Notification
|
||||
from rhodecode.model.notification import NotificationModel
|
||||
from rhodecode.model.meta import Session
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MyAccountNotificationsView(BaseAppView):
|
||||
|
||||
def load_default_context(self):
|
||||
c = self._get_local_tmpl_context()
|
||||
c.user = c.auth_user.get_instance()
|
||||
self._register_global_c(c)
|
||||
return c
|
||||
|
||||
def _has_permissions(self, notification):
|
||||
def is_owner():
|
||||
user_id = self._rhodecode_db_user.user_id
|
||||
for user_notification in notification.notifications_to_users:
|
||||
if user_notification.user.user_id == user_id:
|
||||
return True
|
||||
return False
|
||||
return h.HasPermissionAny('hg.admin')() or is_owner()
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@view_config(
|
||||
route_name='notifications_show_all', request_method='GET',
|
||||
renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
|
||||
def notifications_show_all(self):
|
||||
c = self.load_default_context()
|
||||
|
||||
c.unread_count = NotificationModel().get_unread_cnt_for_user(
|
||||
self._rhodecode_db_user.user_id)
|
||||
|
||||
_current_filter = self.request.GET.getall('type') or ['unread']
|
||||
|
||||
notifications = NotificationModel().get_for_user(
|
||||
self._rhodecode_db_user.user_id,
|
||||
filter_=_current_filter)
|
||||
|
||||
p = safe_int(self.request.GET.get('page', 1), 1)
|
||||
|
||||
def url_generator(**kw):
|
||||
_query = self.request.GET.mixed()
|
||||
_query.update(kw)
|
||||
return self.request.current_route_path(_query=_query)
|
||||
|
||||
c.notifications = Page(notifications, page=p, items_per_page=10,
|
||||
url=url_generator)
|
||||
|
||||
c.unread_type = 'unread'
|
||||
c.all_type = 'all'
|
||||
c.pull_request_type = Notification.TYPE_PULL_REQUEST
|
||||
c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
|
||||
Notification.TYPE_PULL_REQUEST_COMMENT]
|
||||
|
||||
c.current_filter = 'unread' # default filter
|
||||
|
||||
if _current_filter == [c.pull_request_type]:
|
||||
c.current_filter = 'pull_request'
|
||||
elif _current_filter == c.comment_type:
|
||||
c.current_filter = 'comment'
|
||||
elif _current_filter == [c.unread_type]:
|
||||
c.current_filter = 'unread'
|
||||
elif _current_filter == [c.all_type]:
|
||||
c.current_filter = 'all'
|
||||
return self._get_template_context(c)
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@CSRFRequired()
|
||||
@view_config(
|
||||
route_name='notifications_mark_all_read', request_method='POST',
|
||||
renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
|
||||
def notifications_mark_all_read(self):
|
||||
NotificationModel().mark_all_read_for_user(
|
||||
self._rhodecode_db_user.user_id,
|
||||
filter_=self.request.GET.getall('type'))
|
||||
Session().commit()
|
||||
raise HTTPFound(h.route_path('notifications_show_all'))
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@view_config(
|
||||
route_name='notifications_show', request_method='GET',
|
||||
renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
|
||||
def notifications_show(self):
|
||||
c = self.load_default_context()
|
||||
notification_id = self.request.matchdict['notification_id']
|
||||
notification = Notification.get_or_404(notification_id)
|
||||
|
||||
if not self._has_permissions(notification):
|
||||
log.debug('User %s does not have permission to access notification',
|
||||
self._rhodecode_user)
|
||||
raise HTTPNotFound()
|
||||
|
||||
u_notification = NotificationModel().get_user_notification(
|
||||
self._rhodecode_db_user.user_id, notification)
|
||||
if not u_notification:
|
||||
log.debug('User %s notification does not exist',
|
||||
self._rhodecode_user)
|
||||
raise HTTPNotFound()
|
||||
|
||||
# when opening this notification, mark it as read for this use
|
||||
if not u_notification.read:
|
||||
u_notification.mark_as_read()
|
||||
Session().commit()
|
||||
|
||||
c.notification = notification
|
||||
|
||||
return self._get_template_context(c)
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@CSRFRequired()
|
||||
@view_config(
|
||||
route_name='notifications_update', request_method='POST',
|
||||
renderer='json_ext')
|
||||
def notification_update(self):
|
||||
notification_id = self.request.matchdict['notification_id']
|
||||
notification = Notification.get_or_404(notification_id)
|
||||
|
||||
if not self._has_permissions(notification):
|
||||
log.debug('User %s does not have permission to access notification',
|
||||
self._rhodecode_user)
|
||||
raise HTTPNotFound()
|
||||
|
||||
try:
|
||||
# updates notification read flag
|
||||
NotificationModel().mark_read(
|
||||
self._rhodecode_user.user_id, notification)
|
||||
Session().commit()
|
||||
return 'ok'
|
||||
except Exception:
|
||||
Session().rollback()
|
||||
log.exception("Exception updating a notification item")
|
||||
|
||||
raise HTTPInternalServerError()
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
@CSRFRequired()
|
||||
@view_config(
|
||||
route_name='notifications_delete', request_method='POST',
|
||||
renderer='json_ext')
|
||||
def notification_delete(self):
|
||||
notification_id = self.request.matchdict['notification_id']
|
||||
notification = Notification.get_or_404(notification_id)
|
||||
if not self._has_permissions(notification):
|
||||
log.debug('User %s does not have permission to access notification',
|
||||
self._rhodecode_user)
|
||||
raise HTTPNotFound()
|
||||
|
||||
try:
|
||||
# deletes only notification2user
|
||||
NotificationModel().delete(
|
||||
self._rhodecode_user.user_id, notification)
|
||||
Session().commit()
|
||||
return 'ok'
|
||||
except Exception:
|
||||
Session().rollback()
|
||||
log.exception("Exception deleting a notification item")
|
||||
|
||||
raise HTTPInternalServerError()
|
||||
|
|
@ -458,20 +458,6 @@ def make_map(config):
|
|||
m.connect('my_account_password', '/my_account/password',
|
||||
action='my_account_password', conditions={'method': ['GET']})
|
||||
|
||||
# NOTIFICATION REST ROUTES
|
||||
with rmap.submapper(path_prefix=ADMIN_PREFIX,
|
||||
controller='admin/notifications') as m:
|
||||
m.connect('notifications', '/notifications',
|
||||
action='index', conditions={'method': ['GET']})
|
||||
m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
|
||||
action='mark_all_read', conditions={'method': ['POST']})
|
||||
m.connect('/notifications/{notification_id}',
|
||||
action='update', conditions={'method': ['PUT']})
|
||||
m.connect('/notifications/{notification_id}',
|
||||
action='delete', conditions={'method': ['DELETE']})
|
||||
m.connect('notification', '/notifications/{notification_id}',
|
||||
action='show', conditions={'method': ['GET']})
|
||||
|
||||
# USER JOURNAL
|
||||
rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
|
||||
controller='journal', action='index')
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2010-2017 RhodeCode GmbH
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License, version 3
|
||||
# (only), as published by the Free Software Foundation.
|
||||
#
|
||||
# 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 Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# This program is dual-licensed. If you wish to learn more about the
|
||||
# RhodeCode Enterprise Edition, including its added features, Support services,
|
||||
# and proprietary license terms, please see https://rhodecode.com/licenses/
|
||||
|
||||
|
||||
"""
|
||||
notifications controller for RhodeCode
|
||||
"""
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from pylons import request
|
||||
from pylons import tmpl_context as c, url
|
||||
from pylons.controllers.util import redirect, abort
|
||||
import webhelpers.paginate
|
||||
from webob.exc import HTTPBadRequest
|
||||
|
||||
from rhodecode.lib import auth
|
||||
from rhodecode.lib.auth import LoginRequired, NotAnonymous
|
||||
from rhodecode.lib.base import BaseController, render
|
||||
from rhodecode.lib import helpers as h
|
||||
from rhodecode.lib.helpers import Page
|
||||
from rhodecode.lib.utils2 import safe_int
|
||||
from rhodecode.model.db import Notification
|
||||
from rhodecode.model.notification import NotificationModel
|
||||
from rhodecode.model.meta import Session
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NotificationsController(BaseController):
|
||||
"""REST Controller styled on the Atom Publishing Protocol"""
|
||||
|
||||
@LoginRequired()
|
||||
@NotAnonymous()
|
||||
def __before__(self):
|
||||
super(NotificationsController, self).__before__()
|
||||
|
||||
def index(self):
|
||||
"""GET /_admin/notifications: All items in the collection"""
|
||||
# url('notifications')
|
||||
c.user = c.rhodecode_user
|
||||
notif = NotificationModel().get_for_user(
|
||||
c.rhodecode_user.user_id, filter_=request.GET.getall('type'))
|
||||
|
||||
p = safe_int(request.GET.get('page', 1), 1)
|
||||
notifications_url = webhelpers.paginate.PageURL(
|
||||
url('notifications'), request.GET)
|
||||
c.notifications = Page(notif, page=p, items_per_page=10,
|
||||
url=notifications_url)
|
||||
c.pull_request_type = Notification.TYPE_PULL_REQUEST
|
||||
c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
|
||||
Notification.TYPE_PULL_REQUEST_COMMENT]
|
||||
|
||||
_current_filter = request.GET.getall('type')
|
||||
c.current_filter = 'all'
|
||||
if _current_filter == [c.pull_request_type]:
|
||||
c.current_filter = 'pull_request'
|
||||
elif _current_filter == c.comment_type:
|
||||
c.current_filter = 'comment'
|
||||
|
||||
if request.is_xhr:
|
||||
return render('admin/notifications/notifications_data.mako')
|
||||
|
||||
return render('admin/notifications/notifications.mako')
|
||||
|
||||
@auth.CSRFRequired()
|
||||
def mark_all_read(self):
|
||||
if request.is_xhr:
|
||||
nm = NotificationModel()
|
||||
# mark all read
|
||||
nm.mark_all_read_for_user(c.rhodecode_user.user_id,
|
||||
filter_=request.GET.getall('type'))
|
||||
Session().commit()
|
||||
c.user = c.rhodecode_user
|
||||
notif = nm.get_for_user(c.rhodecode_user.user_id,
|
||||
filter_=request.GET.getall('type'))
|
||||
notifications_url = webhelpers.paginate.PageURL(
|
||||
url('notifications'), request.GET)
|
||||
c.notifications = Page(notif, page=1, items_per_page=10,
|
||||
url=notifications_url)
|
||||
return render('admin/notifications/notifications_data.mako')
|
||||
|
||||
def _has_permissions(self, notification):
|
||||
def is_owner():
|
||||
user_id = c.rhodecode_user.user_id
|
||||
for user_notification in notification.notifications_to_users:
|
||||
if user_notification.user.user_id == user_id:
|
||||
return True
|
||||
return False
|
||||
return h.HasPermissionAny('hg.admin')() or is_owner()
|
||||
|
||||
@auth.CSRFRequired()
|
||||
def update(self, notification_id):
|
||||
no = Notification.get_or_404(notification_id)
|
||||
try:
|
||||
if self._has_permissions(no):
|
||||
# deletes only notification2user
|
||||
NotificationModel().mark_read(c.rhodecode_user.user_id, no)
|
||||
Session().commit()
|
||||
return 'ok'
|
||||
except Exception:
|
||||
Session().rollback()
|
||||
log.exception("Exception updating a notification item")
|
||||
raise HTTPBadRequest()
|
||||
|
||||
@auth.CSRFRequired()
|
||||
def delete(self, notification_id):
|
||||
no = Notification.get_or_404(notification_id)
|
||||
try:
|
||||
if self._has_permissions(no):
|
||||
# deletes only notification2user
|
||||
NotificationModel().delete(c.rhodecode_user.user_id, no)
|
||||
Session().commit()
|
||||
return 'ok'
|
||||
except Exception:
|
||||
Session().rollback()
|
||||
log.exception("Exception deleting a notification item")
|
||||
raise HTTPBadRequest()
|
||||
|
||||
def show(self, notification_id):
|
||||
c.user = c.rhodecode_user
|
||||
no = Notification.get_or_404(notification_id)
|
||||
|
||||
if no and self._has_permissions(no):
|
||||
unotification = NotificationModel()\
|
||||
.get_user_notification(c.user.user_id, no)
|
||||
|
||||
# if this association to user is not valid, we don't want to show
|
||||
# this message
|
||||
if unotification:
|
||||
if not unotification.read:
|
||||
unotification.mark_as_read()
|
||||
Session().commit()
|
||||
c.notification = no
|
||||
|
||||
return render('admin/notifications/show_notification.mako')
|
||||
|
||||
return abort(403)
|
||||
|
|
@ -166,9 +166,6 @@ class NotificationModel(BaseModel):
|
|||
def get_for_user(self, user, filter_=None):
|
||||
"""
|
||||
Get mentions for given user, filter them if filter dict is given
|
||||
|
||||
:param user:
|
||||
:param filter:
|
||||
"""
|
||||
user = self._get_user(user)
|
||||
|
||||
|
|
@ -177,11 +174,14 @@ class NotificationModel(BaseModel):
|
|||
.join((
|
||||
Notification, UserNotification.notification_id ==
|
||||
Notification.notification_id))
|
||||
|
||||
if filter_:
|
||||
if filter_ == ['all']:
|
||||
q = q # no filter
|
||||
elif filter_ == ['unread']:
|
||||
q = q.filter(UserNotification.read == false())
|
||||
elif filter_:
|
||||
q = q.filter(Notification.type_.in_(filter_))
|
||||
|
||||
return q.all()
|
||||
return q
|
||||
|
||||
def mark_read(self, user, notification):
|
||||
try:
|
||||
|
|
@ -207,7 +207,9 @@ class NotificationModel(BaseModel):
|
|||
.join((
|
||||
Notification, UserNotification.notification_id ==
|
||||
Notification.notification_id))
|
||||
if filter_:
|
||||
if filter_ == ['unread']:
|
||||
q = q.filter(UserNotification.read == false())
|
||||
elif filter_:
|
||||
q = q.filter(Notification.type_.in_(filter_))
|
||||
|
||||
# this is a little inefficient but sqlalchemy doesn't support
|
||||
|
|
|
|||
|
|
@ -160,6 +160,11 @@ function registerRCRoutes() {
|
|||
pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
|
||||
pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
|
||||
pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
|
||||
pyroutes.register('notifications_show_all', '/_admin/notifications', []);
|
||||
pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
|
||||
pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
|
||||
pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
|
||||
pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
|
||||
pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
|
||||
pyroutes.register('gists_show', '/_admin/gists', []);
|
||||
pyroutes.register('gists_new', '/_admin/gists/new', []);
|
||||
|
|
|
|||
|
|
@ -30,35 +30,37 @@ var _run_callbacks = function(callbacks){
|
|||
}
|
||||
};
|
||||
|
||||
var deleteNotification = function(url, notification_id,callbacks){
|
||||
var deleteNotification = function(notification_id, callbacks){
|
||||
var callback = function(o){
|
||||
var obj = $("#notification_"+notification_id);
|
||||
obj.remove();
|
||||
_run_callbacks(callbacks);
|
||||
};
|
||||
var postData = {'_method': 'delete', 'csrf_token': CSRF_TOKEN};
|
||||
var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
|
||||
var request = $.post(sUrl, postData)
|
||||
.done(callback)
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert("Error while deleting notification.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
|
||||
});
|
||||
var postData = {'csrf_token': CSRF_TOKEN};
|
||||
var sUrl = pyroutes.url('notifications_delete', {'notification_id': notification_id});
|
||||
var request =
|
||||
$.post(sUrl, postData)
|
||||
.done(callback)
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert("Error while deleting notification.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
|
||||
});
|
||||
};
|
||||
|
||||
var readNotification = function(url, notification_id,callbacks){
|
||||
var readNotification = function(notification_id, callbacks){
|
||||
var callback = function(o){
|
||||
var obj = $("#notification_"+notification_id);
|
||||
obj.removeClass('unread');
|
||||
var r_button = $('.read-notification',obj)[0];
|
||||
var r_button = $('.read-notification', obj)[0];
|
||||
r_button.remove();
|
||||
|
||||
_run_callbacks(callbacks);
|
||||
};
|
||||
var postData = {'_method': 'put', 'csrf_token': CSRF_TOKEN};
|
||||
var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
|
||||
var request = $.post(sUrl, postData)
|
||||
.done(callback)
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert("Error while saving notification.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
|
||||
});
|
||||
var postData = {'csrf_token': CSRF_TOKEN};
|
||||
var sUrl = pyroutes.url('notifications_update', {'notification_id': notification_id});
|
||||
var request =
|
||||
$.post(sUrl, postData)
|
||||
.done(callback)
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert("Error while saving notification.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<%inherit file="/base/base.mako"/>
|
||||
|
||||
<%def name="title()">
|
||||
${_('My Notifications')} ${c.rhodecode_user.username}
|
||||
%if c.rhodecode_name:
|
||||
· ${h.branding(c.rhodecode_name)}
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="breadcrumbs_links()">
|
||||
${_('My Notifications')}
|
||||
</%def>
|
||||
|
||||
<%def name="menu_bar_nav()">
|
||||
${self.menu_items(active='admin')}
|
||||
</%def>
|
||||
|
||||
<%def name="main()">
|
||||
<div class="box">
|
||||
<!-- box / title -->
|
||||
<div class="title">
|
||||
${self.breadcrumbs()}
|
||||
##<ul class="links">
|
||||
## <li>
|
||||
## <span ><a href="#">${_('Compose message')}</a></span>
|
||||
## </li>
|
||||
##</ul>
|
||||
|
||||
<div class="notifications_buttons">
|
||||
<span id='all' class="action-link first ${'active' if c.current_filter=='all' else ''}"><a href="${h.url.current()}">${_('All')}</a></span>
|
||||
<span id='comment' class="action-link ${'active' if c.current_filter=='comment' else ''}"><a href="${h.url.current(type=c.comment_type)}">${_('Comments')}</a></span>
|
||||
<span id='pull_request' class="action-link last ${'active' if c.current_filter=='pull_request' else ''}"><a href="${h.url.current(type=c.pull_request_type)}">${_('Pull Requests')}</a></span>
|
||||
|
||||
%if c.notifications:
|
||||
|
||||
<span id='mark_all_read' class="btn btn-default">${_('Mark all as read')}</span>
|
||||
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
<div id='notification_data' class='main-content-full'>
|
||||
<%include file='notifications_data.mako'/>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var url_action = "${h.url('notification', notification_id='__NOTIFICATION_ID__')}";
|
||||
var run = function(){
|
||||
$('#notification_data').on('click','.delete-notification',function(e){
|
||||
var notification_id = e.currentTarget.id;
|
||||
deleteNotification(url_action,notification_id)
|
||||
});
|
||||
$('#notification_data').on('click','.read-notification',function(e){
|
||||
var notification_id = e.currentTarget.id;
|
||||
readNotification(url_action,notification_id)
|
||||
})
|
||||
};
|
||||
run();
|
||||
$('#mark_all_read').on('click',function(e){
|
||||
//set notifications as read
|
||||
var url = "${h.url('notifications_mark_all_read', **request.GET.mixed())}";
|
||||
$.post(url, {'csrf_token': CSRF_TOKEN}).
|
||||
done(function(data){
|
||||
// hide notifications counter
|
||||
$('#quick_login_link > .menu_link_notifications').hide();
|
||||
$('#notification_data').html(data);
|
||||
})
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert("Error while saving notifications.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
|
||||
});
|
||||
});
|
||||
|
||||
var current_filter = $("${c.current_filter}");
|
||||
if (current_filter.length){
|
||||
current_filter.addClass('active');
|
||||
}
|
||||
</script>
|
||||
</%def>
|
||||
|
|
@ -1,40 +1,46 @@
|
|||
<%namespace name="base" file="/base/base.mako"/>
|
||||
%if c.notifications:
|
||||
<%
|
||||
unread = lambda n:{False:'unread'}.get(n)
|
||||
%>
|
||||
|
||||
|
||||
<div class="notification-list notification-table">
|
||||
%for notification in c.notifications:
|
||||
<div id="notification_${notification.notification.notification_id}" class="container ${unread(notification.read)}">
|
||||
<div class="notification-header">
|
||||
<div class="desc ${unread(notification.read)}">
|
||||
<a href="${h.url('notification', notification_id=notification.notification.notification_id)}">
|
||||
${base.gravatar(notification.notification.created_by_user.email, 16)}
|
||||
${notification.notification.description}
|
||||
</a>
|
||||
</div>
|
||||
<div class="delete-notifications">
|
||||
<span id="${notification.notification.notification_id}" class="delete-notification"><i class="icon-delete" ></i></span>
|
||||
</div>
|
||||
<div class="read-notifications">
|
||||
%if not notification.read:
|
||||
<span id="${notification.notification.notification_id}" class="read-notification"><i class="icon-ok" ></i></span>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-subject"></div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">${_('My notifications')}</h3>
|
||||
</div>
|
||||
%endfor
|
||||
</div>
|
||||
|
||||
<div class="notification-paginator">
|
||||
<div class="pagination-wh pagination-left">
|
||||
${c.notifications.pager('$link_previous ~2~ $link_next')}
|
||||
<div class="panel-body">
|
||||
%if c.notifications:
|
||||
|
||||
<div class="notification-list notification-table">
|
||||
%for notification in c.notifications:
|
||||
<div id="notification_${notification.notification.notification_id}" class="container ${'unread' if not notification.read else '' }">
|
||||
<div class="notification-header">
|
||||
<div class="desc ${'unread' if not notification.read else '' }">
|
||||
<a href="${h.route_path('notifications_show', notification_id=notification.notification.notification_id)}">
|
||||
${base.gravatar(notification.notification.created_by_user.email, 16)}
|
||||
${notification.notification.description}
|
||||
</a>
|
||||
</div>
|
||||
<div class="delete-notifications">
|
||||
<span onclick="deleteNotification(${notification.notification.notification_id})" class="delete-notification tooltip" title="${_('Delete')}"><i class="icon-delete"></i></span>
|
||||
</div>
|
||||
<div class="read-notifications">
|
||||
%if not notification.read:
|
||||
<span onclick="readNotification(${notification.notification.notification_id})" class="read-notification tooltip" title="${_('Mark as read')}"><i class="icon-ok"></i></span>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-subject"></div>
|
||||
</div>
|
||||
%endfor
|
||||
</div>
|
||||
|
||||
<div class="notification-paginator">
|
||||
<div class="pagination-wh pagination-left">
|
||||
${c.notifications.pager('$link_previous ~2~ $link_next')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
%else:
|
||||
<div class="table">${_('No notifications here yet')}</div>
|
||||
%endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
%else:
|
||||
<div class="table">${_('No notifications here yet')}</div>
|
||||
%endif
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</%def>
|
||||
|
||||
<%def name="breadcrumbs_links()">
|
||||
${h.link_to(_('Notifications'), h.url('notifications'))}
|
||||
${h.link_to(_('My Notifications'), h.route_path('notifications_show_all'))}
|
||||
»
|
||||
${_('Show notification')}
|
||||
</%def>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
${c.notification.description}
|
||||
</div>
|
||||
<div class="delete-notifications">
|
||||
<span id="${c.notification.notification_id}" class="delete-notification action"><i class="icon-delete" ></i></span>
|
||||
<span class="delete-notification tooltip" title="${_('Delete')}" onclick="deleteNotification(${c.notification.notification_id}, [function(){window.location=pyroutes.url('notifications_show_all')}])" class="delete-notification action"><i class="icon-delete" ></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification-body">
|
||||
|
|
@ -46,12 +46,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var url = "${h.url('notification', notification_id='__NOTIFICATION_ID__')}";
|
||||
var main = "${h.url('notifications')}";
|
||||
$('.delete-notification').on('click',function(e){
|
||||
var notification_id = e.currentTarget.id;
|
||||
deleteNotification(url,notification_id,[function(){window.location=main}])
|
||||
})
|
||||
</script>
|
||||
|
||||
</%def>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<%inherit file="/base/base.mako"/>
|
||||
|
||||
<%def name="title()">
|
||||
${_('My Notifications')} ${c.rhodecode_user.username}
|
||||
%if c.rhodecode_name:
|
||||
· ${h.branding(c.rhodecode_name)}
|
||||
%endif
|
||||
</%def>
|
||||
|
||||
<%def name="breadcrumbs_links()">
|
||||
${_('My Notifications')}
|
||||
</%def>
|
||||
|
||||
<%def name="menu_bar_nav()">
|
||||
${self.menu_items(active='my_account')}
|
||||
</%def>
|
||||
|
||||
<%def name="main()">
|
||||
<div class="box">
|
||||
<div class="title">
|
||||
${self.breadcrumbs()}
|
||||
<div class="notifications_buttons">
|
||||
%if c.notifications:
|
||||
<button id='mark_all_read' class="btn btn-default" type="submit">
|
||||
${_('Mark all as read')}
|
||||
</button>
|
||||
%else:
|
||||
<button class="btn btn-default" type="submit" disabled="disabled">
|
||||
${_('Mark all as read')}
|
||||
</button>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-col-wrapper scw-small">
|
||||
##main
|
||||
<div class="sidebar">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li id='unread' class="${'active' if c.current_filter=='unread' else ''}"><a href="${h.route_path('notifications_show_all', _query=dict(type=c.unread_type))}">${_('Unread')} (${c.unread_count})</a></li>
|
||||
<li id='all' class="${'active' if c.current_filter=='all' else ''}"><a href="${h.route_path('notifications_show_all', _query=dict(type=c.all_type))}">${_('All')}</a></li>
|
||||
<li id='comment' class="${'active' if c.current_filter=='comment' else ''}"><a href="${h.route_path('notifications_show_all', _query=dict(type=c.comment_type))}">${_('Comments')}</a></li>
|
||||
<li id='pull_request' class="${'active' if c.current_filter=='pull_request' else ''}"><a href="${h.route_path('notifications_show_all', _query=dict(type=c.pull_request_type))}">${_('Pull Requests')}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="main-content-full-width">
|
||||
<%include file='notifications_data.mako'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$('#mark_all_read').on('click',function(e){
|
||||
//set notifications as read
|
||||
var url = "${h.route_path('notifications_mark_all_read', _query=request.GET.mixed())}";
|
||||
$.post(url, {'csrf_token': CSRF_TOKEN}).
|
||||
done(function(data){
|
||||
window.location = "${request.current_route_path(_query=request.GET.mixed())}";
|
||||
})
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert("Error while saving notifications.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(this)[0].url));
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</%def>
|
||||
|
|
@ -359,11 +359,7 @@
|
|||
</div>
|
||||
%if c.rhodecode_user.username != h.DEFAULT_USER:
|
||||
<div class="pill_container">
|
||||
% if c.unread_notifications == 0:
|
||||
<a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
|
||||
% else:
|
||||
<a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
|
||||
% endif
|
||||
<a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
|
||||
</div>
|
||||
% endif
|
||||
</li>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue