##// END OF EJS Templates
failsafe the GET `page` argument
marcink -
r2845:6b176c67 beta
parent child Browse files
Show More
@@ -1,59 +1,60 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.admin
3 rhodecode.controllers.admin.admin
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Controller for Admin panel of Rhodecode
6 Controller for Admin panel of Rhodecode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-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
27
28 from pylons import request, tmpl_context as c
28 from pylons import request, tmpl_context as c
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31
31
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.model.db import UserLog
34 from rhodecode.model.db import UserLog
35 from rhodecode.lib.utils2 import safe_int
35
36
36 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
37
38
38
39
39 class AdminController(BaseController):
40 class AdminController(BaseController):
40
41
41 @LoginRequired()
42 @LoginRequired()
42 def __before__(self):
43 def __before__(self):
43 super(AdminController, self).__before__()
44 super(AdminController, self).__before__()
44
45
45 @HasPermissionAllDecorator('hg.admin')
46 @HasPermissionAllDecorator('hg.admin')
46 def index(self):
47 def index(self):
47
48
48 users_log = UserLog.query()\
49 users_log = UserLog.query()\
49 .options(joinedload(UserLog.user))\
50 .options(joinedload(UserLog.user))\
50 .options(joinedload(UserLog.repository))\
51 .options(joinedload(UserLog.repository))\
51 .order_by(UserLog.action_date.desc())
52 .order_by(UserLog.action_date.desc())
52
53
53 p = int(request.params.get('page', 1))
54 p = safe_int(request.params.get('page', 1), 1)
54 c.users_log = Page(users_log, page=p, items_per_page=10)
55 c.users_log = Page(users_log, page=p, items_per_page=10)
55 c.log_data = render('admin/admin_log.html')
56 c.log_data = render('admin/admin_log.html')
56
57
57 if request.environ.get('HTTP_X_PARTIAL_XHR'):
58 if request.environ.get('HTTP_X_PARTIAL_XHR'):
58 return c.log_data
59 return c.log_data
59 return render('admin/admin.html')
60 return render('admin/admin.html')
@@ -1,170 +1,172 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.notifications
3 rhodecode.controllers.admin.notifications
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 notifications controller for RhodeCode
6 notifications controller for RhodeCode
7
7
8 :created_on: Nov 23, 2010
8 :created_on: Nov 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-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 import request
29 from pylons import request
30 from pylons import tmpl_context as c, url
30 from pylons import tmpl_context as c, url
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32
32
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34
34
35 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.model.db import Notification
36 from rhodecode.model.db import Notification
37
37
38 from rhodecode.model.notification import NotificationModel
38 from rhodecode.model.notification import NotificationModel
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.lib.utils2 import safe_int
42
43
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class NotificationsController(BaseController):
48 class NotificationsController(BaseController):
48 """REST Controller styled on the Atom Publishing Protocol"""
49 """REST Controller styled on the Atom Publishing Protocol"""
49 # To properly map this controller, ensure your config/routing.py
50 # To properly map this controller, ensure your config/routing.py
50 # file has a resource setup:
51 # file has a resource setup:
51 # map.resource('notification', 'notifications', controller='_admin/notifications',
52 # map.resource('notification', 'notifications', controller='_admin/notifications',
52 # path_prefix='/_admin', name_prefix='_admin_')
53 # path_prefix='/_admin', name_prefix='_admin_')
53
54
54 @LoginRequired()
55 @LoginRequired()
55 @NotAnonymous()
56 @NotAnonymous()
56 def __before__(self):
57 def __before__(self):
57 super(NotificationsController, self).__before__()
58 super(NotificationsController, self).__before__()
58
59
59 def index(self, format='html'):
60 def index(self, format='html'):
60 """GET /_admin/notifications: All items in the collection"""
61 """GET /_admin/notifications: All items in the collection"""
61 # url('notifications')
62 # url('notifications')
62 c.user = self.rhodecode_user
63 c.user = self.rhodecode_user
63 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
64 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
64 filter_=request.GET.getall('type'))
65 filter_=request.GET.getall('type'))
65 p = int(request.params.get('page', 1))
66
67 p = safe_int(request.params.get('page', 1), 1)
66 c.notifications = Page(notif, page=p, items_per_page=10)
68 c.notifications = Page(notif, page=p, items_per_page=10)
67 c.pull_request_type = Notification.TYPE_PULL_REQUEST
69 c.pull_request_type = Notification.TYPE_PULL_REQUEST
68 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
70 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
69 Notification.TYPE_PULL_REQUEST_COMMENT]
71 Notification.TYPE_PULL_REQUEST_COMMENT]
70
72
71 _current_filter = request.GET.getall('type')
73 _current_filter = request.GET.getall('type')
72 c.current_filter = 'all'
74 c.current_filter = 'all'
73 if _current_filter == [c.pull_request_type]:
75 if _current_filter == [c.pull_request_type]:
74 c.current_filter = 'pull_request'
76 c.current_filter = 'pull_request'
75 elif _current_filter == c.comment_type:
77 elif _current_filter == c.comment_type:
76 c.current_filter = 'comment'
78 c.current_filter = 'comment'
77
79
78 return render('admin/notifications/notifications.html')
80 return render('admin/notifications/notifications.html')
79
81
80 def mark_all_read(self):
82 def mark_all_read(self):
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
83 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 nm = NotificationModel()
84 nm = NotificationModel()
83 # mark all read
85 # mark all read
84 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
86 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
85 filter_=request.GET.getall('type'))
87 filter_=request.GET.getall('type'))
86 Session().commit()
88 Session().commit()
87 c.user = self.rhodecode_user
89 c.user = self.rhodecode_user
88 notif = nm.get_for_user(self.rhodecode_user.user_id,
90 notif = nm.get_for_user(self.rhodecode_user.user_id,
89 filter_=request.GET.getall('type'))
91 filter_=request.GET.getall('type'))
90 c.notifications = Page(notif, page=1, items_per_page=10)
92 c.notifications = Page(notif, page=1, items_per_page=10)
91 return render('admin/notifications/notifications_data.html')
93 return render('admin/notifications/notifications_data.html')
92
94
93 def create(self):
95 def create(self):
94 """POST /_admin/notifications: Create a new item"""
96 """POST /_admin/notifications: Create a new item"""
95 # url('notifications')
97 # url('notifications')
96
98
97 def new(self, format='html'):
99 def new(self, format='html'):
98 """GET /_admin/notifications/new: Form to create a new item"""
100 """GET /_admin/notifications/new: Form to create a new item"""
99 # url('new_notification')
101 # url('new_notification')
100
102
101 def update(self, notification_id):
103 def update(self, notification_id):
102 """PUT /_admin/notifications/id: Update an existing item"""
104 """PUT /_admin/notifications/id: Update an existing item"""
103 # Forms posted to this method should contain a hidden field:
105 # Forms posted to this method should contain a hidden field:
104 # <input type="hidden" name="_method" value="PUT" />
106 # <input type="hidden" name="_method" value="PUT" />
105 # Or using helpers:
107 # Or using helpers:
106 # h.form(url('notification', notification_id=ID),
108 # h.form(url('notification', notification_id=ID),
107 # method='put')
109 # method='put')
108 # url('notification', notification_id=ID)
110 # url('notification', notification_id=ID)
109 try:
111 try:
110 no = Notification.get(notification_id)
112 no = Notification.get(notification_id)
111 owner = lambda: (no.notifications_to_users.user.user_id
113 owner = lambda: (no.notifications_to_users.user.user_id
112 == c.rhodecode_user.user_id)
114 == c.rhodecode_user.user_id)
113 if h.HasPermissionAny('hg.admin')() or owner:
115 if h.HasPermissionAny('hg.admin')() or owner:
114 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
116 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
115 Session().commit()
117 Session().commit()
116 return 'ok'
118 return 'ok'
117 except Exception:
119 except Exception:
118 Session.rollback()
120 Session.rollback()
119 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
120 return 'fail'
122 return 'fail'
121
123
122 def delete(self, notification_id):
124 def delete(self, notification_id):
123 """DELETE /_admin/notifications/id: Delete an existing item"""
125 """DELETE /_admin/notifications/id: Delete an existing item"""
124 # Forms posted to this method should contain a hidden field:
126 # Forms posted to this method should contain a hidden field:
125 # <input type="hidden" name="_method" value="DELETE" />
127 # <input type="hidden" name="_method" value="DELETE" />
126 # Or using helpers:
128 # Or using helpers:
127 # h.form(url('notification', notification_id=ID),
129 # h.form(url('notification', notification_id=ID),
128 # method='delete')
130 # method='delete')
129 # url('notification', notification_id=ID)
131 # url('notification', notification_id=ID)
130
132
131 try:
133 try:
132 no = Notification.get(notification_id)
134 no = Notification.get(notification_id)
133 owner = lambda: (no.notifications_to_users.user.user_id
135 owner = lambda: (no.notifications_to_users.user.user_id
134 == c.rhodecode_user.user_id)
136 == c.rhodecode_user.user_id)
135 if h.HasPermissionAny('hg.admin')() or owner:
137 if h.HasPermissionAny('hg.admin')() or owner:
136 NotificationModel().delete(c.rhodecode_user.user_id, no)
138 NotificationModel().delete(c.rhodecode_user.user_id, no)
137 Session().commit()
139 Session().commit()
138 return 'ok'
140 return 'ok'
139 except Exception:
141 except Exception:
140 Session.rollback()
142 Session.rollback()
141 log.error(traceback.format_exc())
143 log.error(traceback.format_exc())
142 return 'fail'
144 return 'fail'
143
145
144 def show(self, notification_id, format='html'):
146 def show(self, notification_id, format='html'):
145 """GET /_admin/notifications/id: Show a specific item"""
147 """GET /_admin/notifications/id: Show a specific item"""
146 # url('notification', notification_id=ID)
148 # url('notification', notification_id=ID)
147 c.user = self.rhodecode_user
149 c.user = self.rhodecode_user
148 no = Notification.get(notification_id)
150 no = Notification.get(notification_id)
149
151
150 owner = lambda: (no.notifications_to_users.user.user_id
152 owner = lambda: (no.notifications_to_users.user.user_id
151 == c.user.user_id)
153 == c.user.user_id)
152 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
154 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
153 unotification = NotificationModel()\
155 unotification = NotificationModel()\
154 .get_user_notification(c.user.user_id, no)
156 .get_user_notification(c.user.user_id, no)
155
157
156 # if this association to user is not valid, we don't want to show
158 # if this association to user is not valid, we don't want to show
157 # this message
159 # this message
158 if unotification:
160 if unotification:
159 if unotification.read is False:
161 if unotification.read is False:
160 unotification.mark_as_read()
162 unotification.mark_as_read()
161 Session().commit()
163 Session().commit()
162 c.notification = no
164 c.notification = no
163
165
164 return render('admin/notifications/show_notification.html')
166 return render('admin/notifications/show_notification.html')
165
167
166 return redirect(url('notifications'))
168 return redirect(url('notifications'))
167
169
168 def edit(self, notification_id, format='html'):
170 def edit(self, notification_id, format='html'):
169 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
171 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
170 # url('edit_notification', notification_id=ID)
172 # url('edit_notification', notification_id=ID)
@@ -1,124 +1,125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-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 import request, url, session, tmpl_context as c
29 from pylons import request, url, session, tmpl_context as c
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 import rhodecode.lib.helpers as h
33 import rhodecode.lib.helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.graphmod import _colored, _dagwalker
38 from rhodecode.lib.graphmod import _colored, _dagwalker
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
40 from rhodecode.lib.utils2 import safe_int
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43
44
44 class ChangelogController(BaseRepoController):
45 class ChangelogController(BaseRepoController):
45
46
46 @LoginRequired()
47 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 'repository.admin')
49 'repository.admin')
49 def __before__(self):
50 def __before__(self):
50 super(ChangelogController, self).__before__()
51 super(ChangelogController, self).__before__()
51 c.affected_files_cut_off = 60
52 c.affected_files_cut_off = 60
52
53
53 def index(self):
54 def index(self):
54 limit = 100
55 limit = 100
55 default = 20
56 default = 20
56 if request.params.get('size'):
57 if request.params.get('size'):
57 try:
58 try:
58 int_size = int(request.params.get('size'))
59 int_size = int(request.params.get('size'))
59 except ValueError:
60 except ValueError:
60 int_size = default
61 int_size = default
61 c.size = max(min(int_size, limit), 1)
62 c.size = max(min(int_size, limit), 1)
62 session['changelog_size'] = c.size
63 session['changelog_size'] = c.size
63 session.save()
64 session.save()
64 else:
65 else:
65 c.size = int(session.get('changelog_size', default))
66 c.size = int(session.get('changelog_size', default))
66 # min size must be 1
67 # min size must be 1
67 c.size = max(c.size, 1)
68 c.size = max(c.size, 1)
68 p = int(request.params.get('page', 1))
69 p = safe_int(request.params.get('page', 1), 1)
69 branch_name = request.params.get('branch', None)
70 branch_name = request.params.get('branch', None)
70 try:
71 try:
71 if branch_name:
72 if branch_name:
72 collection = [z for z in
73 collection = [z for z in
73 c.rhodecode_repo.get_changesets(start=0,
74 c.rhodecode_repo.get_changesets(start=0,
74 branch_name=branch_name)]
75 branch_name=branch_name)]
75 c.total_cs = len(collection)
76 c.total_cs = len(collection)
76 else:
77 else:
77 collection = c.rhodecode_repo
78 collection = c.rhodecode_repo
78 c.total_cs = len(c.rhodecode_repo)
79 c.total_cs = len(c.rhodecode_repo)
79
80
80 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
81 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
81 items_per_page=c.size, branch=branch_name)
82 items_per_page=c.size, branch=branch_name)
82 collection = list(c.pagination)
83 collection = list(c.pagination)
83 page_revisions = [x.raw_id for x in collection]
84 page_revisions = [x.raw_id for x in collection]
84 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
85 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
85 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
86 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
86 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
87 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
87 log.error(traceback.format_exc())
88 log.error(traceback.format_exc())
88 h.flash(str(e), category='warning')
89 h.flash(str(e), category='warning')
89 return redirect(url('home'))
90 return redirect(url('home'))
90
91
91 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
92 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
92
93
93 c.branch_name = branch_name
94 c.branch_name = branch_name
94 c.branch_filters = [('', _('All Branches'))] + \
95 c.branch_filters = [('', _('All Branches'))] + \
95 [(k, k) for k in c.rhodecode_repo.branches.keys()]
96 [(k, k) for k in c.rhodecode_repo.branches.keys()]
96
97
97 return render('changelog/changelog.html')
98 return render('changelog/changelog.html')
98
99
99 def changelog_details(self, cs):
100 def changelog_details(self, cs):
100 if request.environ.get('HTTP_X_PARTIAL_XHR'):
101 if request.environ.get('HTTP_X_PARTIAL_XHR'):
101 c.cs = c.rhodecode_repo.get_changeset(cs)
102 c.cs = c.rhodecode_repo.get_changeset(cs)
102 return render('changelog/changelog_details.html')
103 return render('changelog/changelog_details.html')
103
104
104 def _graph(self, repo, collection, repo_size, size, p):
105 def _graph(self, repo, collection, repo_size, size, p):
105 """
106 """
106 Generates a DAG graph for mercurial
107 Generates a DAG graph for mercurial
107
108
108 :param repo: repo instance
109 :param repo: repo instance
109 :param size: number of commits to show
110 :param size: number of commits to show
110 :param p: page number
111 :param p: page number
111 """
112 """
112 if not collection:
113 if not collection:
113 c.jsdata = json.dumps([])
114 c.jsdata = json.dumps([])
114 return
115 return
115
116
116 data = []
117 data = []
117 revs = [x.revision for x in collection]
118 revs = [x.revision for x in collection]
118
119
119 dag = _dagwalker(repo, revs, repo.alias)
120 dag = _dagwalker(repo, revs, repo.alias)
120 dag = _colored(dag)
121 dag = _colored(dag)
121 for (id, type, ctx, vtx, edges) in dag:
122 for (id, type, ctx, vtx, edges) in dag:
122 data.append(['', vtx, edges])
123 data.append(['', vtx, edges])
123
124
124 c.jsdata = json.dumps(data)
125 c.jsdata = json.dumps(data)
@@ -1,57 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.followers
3 rhodecode.controllers.followers
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Followers controller for rhodecode
6 Followers controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 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 import logging
25 import logging
26
26
27 from pylons import tmpl_context as c, request
27 from pylons import tmpl_context as c, request
28
28
29 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.model.db import Repository, User, UserFollowing
32 from rhodecode.model.db import Repository, User, UserFollowing
33 from rhodecode.lib.utils2 import safe_int
33
34
34 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
35
36
36
37
37 class FollowersController(BaseRepoController):
38 class FollowersController(BaseRepoController):
38
39
39 @LoginRequired()
40 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
42 'repository.admin')
42 def __before__(self):
43 def __before__(self):
43 super(FollowersController, self).__before__()
44 super(FollowersController, self).__before__()
44
45
45 def followers(self, repo_name):
46 def followers(self, repo_name):
46 p = int(request.params.get('page', 1))
47 p = safe_int(request.params.get('page', 1), 1)
47 repo_id = c.rhodecode_db_repo.repo_id
48 repo_id = c.rhodecode_db_repo.repo_id
48 d = UserFollowing.get_repo_followers(repo_id)\
49 d = UserFollowing.get_repo_followers(repo_id)\
49 .order_by(UserFollowing.follows_from)
50 .order_by(UserFollowing.follows_from)
50 c.followers_pager = Page(d, page=p, items_per_page=20)
51 c.followers_pager = Page(d, page=p, items_per_page=20)
51
52
52 c.followers_data = render('/followers/followers_data.html')
53 c.followers_data = render('/followers/followers_data.html')
53
54
54 if request.environ.get('HTTP_X_PARTIAL_XHR'):
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
55 return c.followers_data
56 return c.followers_data
56
57
57 return render('/followers/followers.html')
58 return render('/followers/followers.html')
@@ -1,184 +1,185 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.forks
3 rhodecode.controllers.forks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 forks controller for rhodecode
6 forks controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 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 import logging
25 import logging
26 import formencode
26 import formencode
27 import traceback
27 import traceback
28 from formencode import htmlfill
28 from formencode import htmlfill
29
29
30 from pylons import tmpl_context as c, request, url
30 from pylons import tmpl_context as c, request, url
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35
35
36 from rhodecode.lib.helpers import Page
36 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.forms import RepoForkForm
43 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
45 from rhodecode.lib.utils2 import safe_int
45
46
46 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
47
48
48
49
49 class ForksController(BaseRepoController):
50 class ForksController(BaseRepoController):
50
51
51 @LoginRequired()
52 @LoginRequired()
52 def __before__(self):
53 def __before__(self):
53 super(ForksController, self).__before__()
54 super(ForksController, self).__before__()
54
55
55 def __load_defaults(self):
56 def __load_defaults(self):
56 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
57 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
59 c.landing_revs_choices = choices
60 c.landing_revs_choices = choices
60
61
61 def __load_data(self, repo_name=None):
62 def __load_data(self, repo_name=None):
62 """
63 """
63 Load defaults settings for edit, and update
64 Load defaults settings for edit, and update
64
65
65 :param repo_name:
66 :param repo_name:
66 """
67 """
67 self.__load_defaults()
68 self.__load_defaults()
68
69
69 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
70 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
70 repo = db_repo.scm_instance
71 repo = db_repo.scm_instance
71
72
72 if c.repo_info is None:
73 if c.repo_info is None:
73 h.flash(_('%s repository is not mapped to db perhaps'
74 h.flash(_('%s repository is not mapped to db perhaps'
74 ' it was created or renamed from the filesystem'
75 ' it was created or renamed from the filesystem'
75 ' please run the application again'
76 ' please run the application again'
76 ' in order to rescan repositories') % repo_name,
77 ' in order to rescan repositories') % repo_name,
77 category='error')
78 category='error')
78
79
79 return redirect(url('repos'))
80 return redirect(url('repos'))
80
81
81 c.default_user_id = User.get_by_username('default').user_id
82 c.default_user_id = User.get_by_username('default').user_id
82 c.in_public_journal = UserFollowing.query()\
83 c.in_public_journal = UserFollowing.query()\
83 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85
86
86 if c.repo_info.stats:
87 if c.repo_info.stats:
87 last_rev = c.repo_info.stats.stat_on_revision+1
88 last_rev = c.repo_info.stats.stat_on_revision+1
88 else:
89 else:
89 last_rev = 0
90 last_rev = 0
90 c.stats_revision = last_rev
91 c.stats_revision = last_rev
91
92
92 c.repo_last_rev = repo.count() if repo.revisions else 0
93 c.repo_last_rev = repo.count() if repo.revisions else 0
93
94
94 if last_rev == 0 or c.repo_last_rev == 0:
95 if last_rev == 0 or c.repo_last_rev == 0:
95 c.stats_percentage = 0
96 c.stats_percentage = 0
96 else:
97 else:
97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.repo_last_rev) * 100)
99 c.repo_last_rev) * 100)
99
100
100 defaults = RepoModel()._get_defaults(repo_name)
101 defaults = RepoModel()._get_defaults(repo_name)
101 # add prefix to fork
102 # add prefix to fork
102 defaults['repo_name'] = 'fork-' + defaults['repo_name']
103 defaults['repo_name'] = 'fork-' + defaults['repo_name']
103 return defaults
104 return defaults
104
105
105 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
106 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
106 'repository.admin')
107 'repository.admin')
107 def forks(self, repo_name):
108 def forks(self, repo_name):
108 p = int(request.params.get('page', 1))
109 p = safe_int(request.params.get('page', 1), 1)
109 repo_id = c.rhodecode_db_repo.repo_id
110 repo_id = c.rhodecode_db_repo.repo_id
110 d = []
111 d = []
111 for r in Repository.get_repo_forks(repo_id):
112 for r in Repository.get_repo_forks(repo_id):
112 if not HasRepoPermissionAny(
113 if not HasRepoPermissionAny(
113 'repository.read', 'repository.write', 'repository.admin'
114 'repository.read', 'repository.write', 'repository.admin'
114 )(r.repo_name, 'get forks check'):
115 )(r.repo_name, 'get forks check'):
115 continue
116 continue
116 d.append(r)
117 d.append(r)
117 c.forks_pager = Page(d, page=p, items_per_page=20)
118 c.forks_pager = Page(d, page=p, items_per_page=20)
118
119
119 c.forks_data = render('/forks/forks_data.html')
120 c.forks_data = render('/forks/forks_data.html')
120
121
121 if request.environ.get('HTTP_X_PARTIAL_XHR'):
122 if request.environ.get('HTTP_X_PARTIAL_XHR'):
122 return c.forks_data
123 return c.forks_data
123
124
124 return render('/forks/forks.html')
125 return render('/forks/forks.html')
125
126
126 @NotAnonymous()
127 @NotAnonymous()
127 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
128 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
128 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
129 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
129 'repository.admin')
130 'repository.admin')
130 def fork(self, repo_name):
131 def fork(self, repo_name):
131 c.repo_info = Repository.get_by_repo_name(repo_name)
132 c.repo_info = Repository.get_by_repo_name(repo_name)
132 if not c.repo_info:
133 if not c.repo_info:
133 h.flash(_('%s repository is not mapped to db perhaps'
134 h.flash(_('%s repository is not mapped to db perhaps'
134 ' it was created or renamed from the file system'
135 ' it was created or renamed from the file system'
135 ' please run the application again'
136 ' please run the application again'
136 ' in order to rescan repositories') % repo_name,
137 ' in order to rescan repositories') % repo_name,
137 category='error')
138 category='error')
138
139
139 return redirect(url('home'))
140 return redirect(url('home'))
140
141
141 defaults = self.__load_data(repo_name)
142 defaults = self.__load_data(repo_name)
142
143
143 return htmlfill.render(
144 return htmlfill.render(
144 render('forks/fork.html'),
145 render('forks/fork.html'),
145 defaults=defaults,
146 defaults=defaults,
146 encoding="UTF-8",
147 encoding="UTF-8",
147 force_defaults=False
148 force_defaults=False
148 )
149 )
149
150
150 @NotAnonymous()
151 @NotAnonymous()
151 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 'repository.admin')
154 'repository.admin')
154 def fork_create(self, repo_name):
155 def fork_create(self, repo_name):
155 self.__load_defaults()
156 self.__load_defaults()
156 c.repo_info = Repository.get_by_repo_name(repo_name)
157 c.repo_info = Repository.get_by_repo_name(repo_name)
157 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 repo_groups=c.repo_groups_choices,
159 repo_groups=c.repo_groups_choices,
159 landing_revs=c.landing_revs_choices)()
160 landing_revs=c.landing_revs_choices)()
160 form_result = {}
161 form_result = {}
161 try:
162 try:
162 form_result = _form.to_python(dict(request.POST))
163 form_result = _form.to_python(dict(request.POST))
163
164
164 # create fork is done sometimes async on celery, db transaction
165 # create fork is done sometimes async on celery, db transaction
165 # management is handled there.
166 # management is handled there.
166 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
167 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
167 h.flash(_('forked %s repository as %s') \
168 h.flash(_('forked %s repository as %s') \
168 % (repo_name, form_result['repo_name']),
169 % (repo_name, form_result['repo_name']),
169 category='success')
170 category='success')
170 except formencode.Invalid, errors:
171 except formencode.Invalid, errors:
171 c.new_repo = errors.value['repo_name']
172 c.new_repo = errors.value['repo_name']
172
173
173 return htmlfill.render(
174 return htmlfill.render(
174 render('forks/fork.html'),
175 render('forks/fork.html'),
175 defaults=errors.value,
176 defaults=errors.value,
176 errors=errors.error_dict or {},
177 errors=errors.error_dict or {},
177 prefix_error=False,
178 prefix_error=False,
178 encoding="UTF-8")
179 encoding="UTF-8")
179 except Exception:
180 except Exception:
180 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
181 h.flash(_('An error occurred during repository forking %s') %
182 h.flash(_('An error occurred during repository forking %s') %
182 repo_name, category='error')
183 repo_name, category='error')
183
184
184 return redirect(url('home'))
185 return redirect(url('home'))
@@ -1,294 +1,295 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-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 import logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
32
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 import rhodecode.lib.helpers as h
37 import rhodecode.lib.helpers as h
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from sqlalchemy.sql.expression import func
42 from sqlalchemy.sql.expression import func
43 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.scm import ScmModel
44 from rhodecode.lib.utils2 import safe_int
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47
48
48 class JournalController(BaseController):
49 class JournalController(BaseController):
49
50
50 def __before__(self):
51 def __before__(self):
51 super(JournalController, self).__before__()
52 super(JournalController, self).__before__()
52 self.language = 'en-us'
53 self.language = 'en-us'
53 self.ttl = "5"
54 self.ttl = "5"
54 self.feed_nr = 20
55 self.feed_nr = 20
55
56
56 @LoginRequired()
57 @LoginRequired()
57 @NotAnonymous()
58 @NotAnonymous()
58 def index(self):
59 def index(self):
59 # Return a rendered template
60 # Return a rendered template
60 p = int(request.params.get('page', 1))
61 p = safe_int(request.params.get('page', 1), 1)
61
62
62 c.user = User.get(self.rhodecode_user.user_id)
63 c.user = User.get(self.rhodecode_user.user_id)
63 all_repos = self.sa.query(Repository)\
64 all_repos = self.sa.query(Repository)\
64 .filter(Repository.user_id == c.user.user_id)\
65 .filter(Repository.user_id == c.user.user_id)\
65 .order_by(func.lower(Repository.repo_name)).all()
66 .order_by(func.lower(Repository.repo_name)).all()
66
67
67 c.user_repos = ScmModel().get_repos(all_repos)
68 c.user_repos = ScmModel().get_repos(all_repos)
68
69
69 c.following = self.sa.query(UserFollowing)\
70 c.following = self.sa.query(UserFollowing)\
70 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
71 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
71 .options(joinedload(UserFollowing.follows_repository))\
72 .options(joinedload(UserFollowing.follows_repository))\
72 .all()
73 .all()
73
74
74 journal = self._get_journal_data(c.following)
75 journal = self._get_journal_data(c.following)
75
76
76 c.journal_pager = Page(journal, page=p, items_per_page=20)
77 c.journal_pager = Page(journal, page=p, items_per_page=20)
77
78
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79
80
80 c.journal_data = render('journal/journal_data.html')
81 c.journal_data = render('journal/journal_data.html')
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 return c.journal_data
83 return c.journal_data
83 return render('journal/journal.html')
84 return render('journal/journal.html')
84
85
85 @LoginRequired(api_access=True)
86 @LoginRequired(api_access=True)
86 @NotAnonymous()
87 @NotAnonymous()
87 def journal_atom(self):
88 def journal_atom(self):
88 """
89 """
89 Produce an atom-1.0 feed via feedgenerator module
90 Produce an atom-1.0 feed via feedgenerator module
90 """
91 """
91 following = self.sa.query(UserFollowing)\
92 following = self.sa.query(UserFollowing)\
92 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
93 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
93 .options(joinedload(UserFollowing.follows_repository))\
94 .options(joinedload(UserFollowing.follows_repository))\
94 .all()
95 .all()
95 return self._atom_feed(following, public=False)
96 return self._atom_feed(following, public=False)
96
97
97 @LoginRequired(api_access=True)
98 @LoginRequired(api_access=True)
98 @NotAnonymous()
99 @NotAnonymous()
99 def journal_rss(self):
100 def journal_rss(self):
100 """
101 """
101 Produce an rss feed via feedgenerator module
102 Produce an rss feed via feedgenerator module
102 """
103 """
103 following = self.sa.query(UserFollowing)\
104 following = self.sa.query(UserFollowing)\
104 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
105 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
105 .options(joinedload(UserFollowing.follows_repository))\
106 .options(joinedload(UserFollowing.follows_repository))\
106 .all()
107 .all()
107 return self._rss_feed(following, public=False)
108 return self._rss_feed(following, public=False)
108
109
109 def _get_daily_aggregate(self, journal):
110 def _get_daily_aggregate(self, journal):
110 groups = []
111 groups = []
111 for k, g in groupby(journal, lambda x: x.action_as_day):
112 for k, g in groupby(journal, lambda x: x.action_as_day):
112 user_group = []
113 user_group = []
113 for k2, g2 in groupby(list(g), lambda x: x.user.email):
114 for k2, g2 in groupby(list(g), lambda x: x.user.email):
114 l = list(g2)
115 l = list(g2)
115 user_group.append((l[0].user, l))
116 user_group.append((l[0].user, l))
116
117
117 groups.append((k, user_group,))
118 groups.append((k, user_group,))
118
119
119 return groups
120 return groups
120
121
121 def _get_journal_data(self, following_repos):
122 def _get_journal_data(self, following_repos):
122 repo_ids = [x.follows_repository.repo_id for x in following_repos
123 repo_ids = [x.follows_repository.repo_id for x in following_repos
123 if x.follows_repository is not None]
124 if x.follows_repository is not None]
124 user_ids = [x.follows_user.user_id for x in following_repos
125 user_ids = [x.follows_user.user_id for x in following_repos
125 if x.follows_user is not None]
126 if x.follows_user is not None]
126
127
127 filtering_criterion = None
128 filtering_criterion = None
128
129
129 if repo_ids and user_ids:
130 if repo_ids and user_ids:
130 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
131 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
131 UserLog.user_id.in_(user_ids))
132 UserLog.user_id.in_(user_ids))
132 if repo_ids and not user_ids:
133 if repo_ids and not user_ids:
133 filtering_criterion = UserLog.repository_id.in_(repo_ids)
134 filtering_criterion = UserLog.repository_id.in_(repo_ids)
134 if not repo_ids and user_ids:
135 if not repo_ids and user_ids:
135 filtering_criterion = UserLog.user_id.in_(user_ids)
136 filtering_criterion = UserLog.user_id.in_(user_ids)
136 if filtering_criterion is not None:
137 if filtering_criterion is not None:
137 journal = self.sa.query(UserLog)\
138 journal = self.sa.query(UserLog)\
138 .options(joinedload(UserLog.user))\
139 .options(joinedload(UserLog.user))\
139 .options(joinedload(UserLog.repository))\
140 .options(joinedload(UserLog.repository))\
140 .filter(filtering_criterion)\
141 .filter(filtering_criterion)\
141 .order_by(UserLog.action_date.desc())
142 .order_by(UserLog.action_date.desc())
142 else:
143 else:
143 journal = []
144 journal = []
144
145
145 return journal
146 return journal
146
147
147 @LoginRequired()
148 @LoginRequired()
148 @NotAnonymous()
149 @NotAnonymous()
149 def toggle_following(self):
150 def toggle_following(self):
150 cur_token = request.POST.get('auth_token')
151 cur_token = request.POST.get('auth_token')
151 token = h.get_token()
152 token = h.get_token()
152 if cur_token == token:
153 if cur_token == token:
153
154
154 user_id = request.POST.get('follows_user_id')
155 user_id = request.POST.get('follows_user_id')
155 if user_id:
156 if user_id:
156 try:
157 try:
157 self.scm_model.toggle_following_user(user_id,
158 self.scm_model.toggle_following_user(user_id,
158 self.rhodecode_user.user_id)
159 self.rhodecode_user.user_id)
159 Session.commit()
160 Session.commit()
160 return 'ok'
161 return 'ok'
161 except:
162 except:
162 raise HTTPBadRequest()
163 raise HTTPBadRequest()
163
164
164 repo_id = request.POST.get('follows_repo_id')
165 repo_id = request.POST.get('follows_repo_id')
165 if repo_id:
166 if repo_id:
166 try:
167 try:
167 self.scm_model.toggle_following_repo(repo_id,
168 self.scm_model.toggle_following_repo(repo_id,
168 self.rhodecode_user.user_id)
169 self.rhodecode_user.user_id)
169 Session.commit()
170 Session.commit()
170 return 'ok'
171 return 'ok'
171 except:
172 except:
172 raise HTTPBadRequest()
173 raise HTTPBadRequest()
173
174
174 log.debug('token mismatch %s vs %s' % (cur_token, token))
175 log.debug('token mismatch %s vs %s' % (cur_token, token))
175 raise HTTPBadRequest()
176 raise HTTPBadRequest()
176
177
177 @LoginRequired()
178 @LoginRequired()
178 def public_journal(self):
179 def public_journal(self):
179 # Return a rendered template
180 # Return a rendered template
180 p = int(request.params.get('page', 1))
181 p = safe_int(request.params.get('page', 1), 1)
181
182
182 c.following = self.sa.query(UserFollowing)\
183 c.following = self.sa.query(UserFollowing)\
183 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
184 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
184 .options(joinedload(UserFollowing.follows_repository))\
185 .options(joinedload(UserFollowing.follows_repository))\
185 .all()
186 .all()
186
187
187 journal = self._get_journal_data(c.following)
188 journal = self._get_journal_data(c.following)
188
189
189 c.journal_pager = Page(journal, page=p, items_per_page=20)
190 c.journal_pager = Page(journal, page=p, items_per_page=20)
190
191
191 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
192 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
192
193
193 c.journal_data = render('journal/journal_data.html')
194 c.journal_data = render('journal/journal_data.html')
194 if request.environ.get('HTTP_X_PARTIAL_XHR'):
195 if request.environ.get('HTTP_X_PARTIAL_XHR'):
195 return c.journal_data
196 return c.journal_data
196 return render('journal/public_journal.html')
197 return render('journal/public_journal.html')
197
198
198 def _atom_feed(self, repos, public=True):
199 def _atom_feed(self, repos, public=True):
199 journal = self._get_journal_data(repos)
200 journal = self._get_journal_data(repos)
200 if public:
201 if public:
201 _link = url('public_journal_atom', qualified=True)
202 _link = url('public_journal_atom', qualified=True)
202 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
203 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
203 'atom feed')
204 'atom feed')
204 else:
205 else:
205 _link = url('journal_atom', qualified=True)
206 _link = url('journal_atom', qualified=True)
206 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
207 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
207
208
208 feed = Atom1Feed(title=_desc,
209 feed = Atom1Feed(title=_desc,
209 link=_link,
210 link=_link,
210 description=_desc,
211 description=_desc,
211 language=self.language,
212 language=self.language,
212 ttl=self.ttl)
213 ttl=self.ttl)
213
214
214 for entry in journal[:self.feed_nr]:
215 for entry in journal[:self.feed_nr]:
215 action, action_extra, ico = h.action_parser(entry, feed=True)
216 action, action_extra, ico = h.action_parser(entry, feed=True)
216 title = "%s - %s %s" % (entry.user.short_contact, action(),
217 title = "%s - %s %s" % (entry.user.short_contact, action(),
217 entry.repository.repo_name)
218 entry.repository.repo_name)
218 desc = action_extra()
219 desc = action_extra()
219 _url = None
220 _url = None
220 if entry.repository is not None:
221 if entry.repository is not None:
221 _url = url('changelog_home',
222 _url = url('changelog_home',
222 repo_name=entry.repository.repo_name,
223 repo_name=entry.repository.repo_name,
223 qualified=True)
224 qualified=True)
224
225
225 feed.add_item(title=title,
226 feed.add_item(title=title,
226 pubdate=entry.action_date,
227 pubdate=entry.action_date,
227 link=_url or url('', qualified=True),
228 link=_url or url('', qualified=True),
228 author_email=entry.user.email,
229 author_email=entry.user.email,
229 author_name=entry.user.full_contact,
230 author_name=entry.user.full_contact,
230 description=desc)
231 description=desc)
231
232
232 response.content_type = feed.mime_type
233 response.content_type = feed.mime_type
233 return feed.writeString('utf-8')
234 return feed.writeString('utf-8')
234
235
235 def _rss_feed(self, repos, public=True):
236 def _rss_feed(self, repos, public=True):
236 journal = self._get_journal_data(repos)
237 journal = self._get_journal_data(repos)
237 if public:
238 if public:
238 _link = url('public_journal_atom', qualified=True)
239 _link = url('public_journal_atom', qualified=True)
239 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
240 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
240 'rss feed')
241 'rss feed')
241 else:
242 else:
242 _link = url('journal_atom', qualified=True)
243 _link = url('journal_atom', qualified=True)
243 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
244 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
244
245
245 feed = Rss201rev2Feed(title=_desc,
246 feed = Rss201rev2Feed(title=_desc,
246 link=_link,
247 link=_link,
247 description=_desc,
248 description=_desc,
248 language=self.language,
249 language=self.language,
249 ttl=self.ttl)
250 ttl=self.ttl)
250
251
251 for entry in journal[:self.feed_nr]:
252 for entry in journal[:self.feed_nr]:
252 action, action_extra, ico = h.action_parser(entry, feed=True)
253 action, action_extra, ico = h.action_parser(entry, feed=True)
253 title = "%s - %s %s" % (entry.user.short_contact, action(),
254 title = "%s - %s %s" % (entry.user.short_contact, action(),
254 entry.repository.repo_name)
255 entry.repository.repo_name)
255 desc = action_extra()
256 desc = action_extra()
256 _url = None
257 _url = None
257 if entry.repository is not None:
258 if entry.repository is not None:
258 _url = url('changelog_home',
259 _url = url('changelog_home',
259 repo_name=entry.repository.repo_name,
260 repo_name=entry.repository.repo_name,
260 qualified=True)
261 qualified=True)
261
262
262 feed.add_item(title=title,
263 feed.add_item(title=title,
263 pubdate=entry.action_date,
264 pubdate=entry.action_date,
264 link=_url or url('', qualified=True),
265 link=_url or url('', qualified=True),
265 author_email=entry.user.email,
266 author_email=entry.user.email,
266 author_name=entry.user.full_contact,
267 author_name=entry.user.full_contact,
267 description=desc)
268 description=desc)
268
269
269 response.content_type = feed.mime_type
270 response.content_type = feed.mime_type
270 return feed.writeString('utf-8')
271 return feed.writeString('utf-8')
271
272
272 @LoginRequired(api_access=True)
273 @LoginRequired(api_access=True)
273 def public_journal_atom(self):
274 def public_journal_atom(self):
274 """
275 """
275 Produce an atom-1.0 feed via feedgenerator module
276 Produce an atom-1.0 feed via feedgenerator module
276 """
277 """
277 c.following = self.sa.query(UserFollowing)\
278 c.following = self.sa.query(UserFollowing)\
278 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
279 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
279 .options(joinedload(UserFollowing.follows_repository))\
280 .options(joinedload(UserFollowing.follows_repository))\
280 .all()
281 .all()
281
282
282 return self._atom_feed(c.following)
283 return self._atom_feed(c.following)
283
284
284 @LoginRequired(api_access=True)
285 @LoginRequired(api_access=True)
285 def public_journal_rss(self):
286 def public_journal_rss(self):
286 """
287 """
287 Produce an rss2 feed via feedgenerator module
288 Produce an rss2 feed via feedgenerator module
288 """
289 """
289 c.following = self.sa.query(UserFollowing)\
290 c.following = self.sa.query(UserFollowing)\
290 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
291 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
291 .options(joinedload(UserFollowing.follows_repository))\
292 .options(joinedload(UserFollowing.follows_repository))\
292 .all()
293 .all()
293
294
294 return self._rss_feed(c.following)
295 return self._rss_feed(c.following)
@@ -1,144 +1,144 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.search
3 rhodecode.controllers.search
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Search controller for RhodeCode
6 Search controller for RhodeCode
7
7
8 :created_on: Aug 7, 2010
8 :created_on: Aug 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-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 import logging
25 import logging
26 import traceback
26 import traceback
27
27
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons import request, config, tmpl_context as c
29 from pylons import request, config, tmpl_context as c
30
30
31 from rhodecode.lib.auth import LoginRequired
31 from rhodecode.lib.auth import LoginRequired
32 from rhodecode.lib.base import BaseController, render
32 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
34 IDX_NAME, WhooshResultWrapper
34 IDX_NAME, WhooshResultWrapper
35
35
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.util import update_params
37 from webhelpers.util import update_params
38
38
39 from whoosh.index import open_dir, EmptyIndexError
39 from whoosh.index import open_dir, EmptyIndexError
40 from whoosh.qparser import QueryParser, QueryParserError
40 from whoosh.qparser import QueryParser, QueryParserError
41 from whoosh.query import Phrase, Wildcard, Term, Prefix
41 from whoosh.query import Phrase, Wildcard, Term, Prefix
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str, safe_int
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class SearchController(BaseController):
48 class SearchController(BaseController):
49
49
50 @LoginRequired()
50 @LoginRequired()
51 def __before__(self):
51 def __before__(self):
52 super(SearchController, self).__before__()
52 super(SearchController, self).__before__()
53
53
54 def index(self, search_repo=None):
54 def index(self, search_repo=None):
55 c.repo_name = search_repo
55 c.repo_name = search_repo
56 c.formated_results = []
56 c.formated_results = []
57 c.runtime = ''
57 c.runtime = ''
58 c.cur_query = request.GET.get('q', None)
58 c.cur_query = request.GET.get('q', None)
59 c.cur_type = request.GET.get('type', 'content')
59 c.cur_type = request.GET.get('type', 'content')
60 c.cur_search = search_type = {'content': 'content',
60 c.cur_search = search_type = {'content': 'content',
61 'commit': 'message',
61 'commit': 'message',
62 'path': 'path',
62 'path': 'path',
63 'repository': 'repository'
63 'repository': 'repository'
64 }.get(c.cur_type, 'content')
64 }.get(c.cur_type, 'content')
65
65
66 index_name = {
66 index_name = {
67 'content': IDX_NAME,
67 'content': IDX_NAME,
68 'commit': CHGSET_IDX_NAME,
68 'commit': CHGSET_IDX_NAME,
69 'path': IDX_NAME
69 'path': IDX_NAME
70 }.get(c.cur_type, IDX_NAME)
70 }.get(c.cur_type, IDX_NAME)
71
71
72 schema_defn = {
72 schema_defn = {
73 'content': SCHEMA,
73 'content': SCHEMA,
74 'commit': CHGSETS_SCHEMA,
74 'commit': CHGSETS_SCHEMA,
75 'path': SCHEMA
75 'path': SCHEMA
76 }.get(c.cur_type, SCHEMA)
76 }.get(c.cur_type, SCHEMA)
77
77
78 log.debug('IDX: %s' % index_name)
78 log.debug('IDX: %s' % index_name)
79 log.debug('SCHEMA: %s' % schema_defn)
79 log.debug('SCHEMA: %s' % schema_defn)
80
80
81 if c.cur_query:
81 if c.cur_query:
82 cur_query = c.cur_query.lower()
82 cur_query = c.cur_query.lower()
83 log.debug(cur_query)
83 log.debug(cur_query)
84
84
85 if c.cur_query:
85 if c.cur_query:
86 p = int(request.params.get('page', 1))
86 p = safe_int(request.params.get('page', 1), 1)
87 highlight_items = set()
87 highlight_items = set()
88 try:
88 try:
89 idx = open_dir(config['app_conf']['index_dir'],
89 idx = open_dir(config['app_conf']['index_dir'],
90 indexname=index_name)
90 indexname=index_name)
91 searcher = idx.searcher()
91 searcher = idx.searcher()
92
92
93 qp = QueryParser(search_type, schema=schema_defn)
93 qp = QueryParser(search_type, schema=schema_defn)
94 if c.repo_name:
94 if c.repo_name:
95 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
95 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
96 try:
96 try:
97 query = qp.parse(unicode(cur_query))
97 query = qp.parse(unicode(cur_query))
98 # extract words for highlight
98 # extract words for highlight
99 if isinstance(query, Phrase):
99 if isinstance(query, Phrase):
100 highlight_items.update(query.words)
100 highlight_items.update(query.words)
101 elif isinstance(query, Prefix):
101 elif isinstance(query, Prefix):
102 highlight_items.add(query.text)
102 highlight_items.add(query.text)
103 else:
103 else:
104 for i in query.all_terms():
104 for i in query.all_terms():
105 if i[0] in ['content', 'message']:
105 if i[0] in ['content', 'message']:
106 highlight_items.add(i[1])
106 highlight_items.add(i[1])
107
107
108 matcher = query.matcher(searcher)
108 matcher = query.matcher(searcher)
109
109
110 log.debug('query: %s' % query)
110 log.debug('query: %s' % query)
111 log.debug('hl terms: %s' % highlight_items)
111 log.debug('hl terms: %s' % highlight_items)
112 results = searcher.search(query)
112 results = searcher.search(query)
113 res_ln = len(results)
113 res_ln = len(results)
114 c.runtime = '%s results (%.3f seconds)' % (
114 c.runtime = '%s results (%.3f seconds)' % (
115 res_ln, results.runtime
115 res_ln, results.runtime
116 )
116 )
117
117
118 def url_generator(**kw):
118 def url_generator(**kw):
119 return update_params("?q=%s&type=%s" \
119 return update_params("?q=%s&type=%s" \
120 % (safe_str(c.cur_query), safe_str(c.cur_type)), **kw)
120 % (safe_str(c.cur_query), safe_str(c.cur_type)), **kw)
121 repo_location = RepoModel().repos_path
121 repo_location = RepoModel().repos_path
122 c.formated_results = Page(
122 c.formated_results = Page(
123 WhooshResultWrapper(search_type, searcher, matcher,
123 WhooshResultWrapper(search_type, searcher, matcher,
124 highlight_items, repo_location),
124 highlight_items, repo_location),
125 page=p,
125 page=p,
126 item_count=res_ln,
126 item_count=res_ln,
127 items_per_page=10,
127 items_per_page=10,
128 url=url_generator
128 url=url_generator
129 )
129 )
130
130
131 except QueryParserError:
131 except QueryParserError:
132 c.runtime = _('Invalid search query. Try quoting it.')
132 c.runtime = _('Invalid search query. Try quoting it.')
133 searcher.close()
133 searcher.close()
134 except (EmptyIndexError, IOError):
134 except (EmptyIndexError, IOError):
135 log.error(traceback.format_exc())
135 log.error(traceback.format_exc())
136 log.error('Empty Index data')
136 log.error('Empty Index data')
137 c.runtime = _('There is no index to search in. '
137 c.runtime = _('There is no index to search in. '
138 'Please run whoosh indexer')
138 'Please run whoosh indexer')
139 except (Exception):
139 except (Exception):
140 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
141 c.runtime = _('An error occurred during this search operation')
141 c.runtime = _('An error occurred during this search operation')
142
142
143 # Return a rendered template
143 # Return a rendered template
144 return render('/search/search.html')
144 return render('/search/search.html')
@@ -1,63 +1,64 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.shortlog
3 rhodecode.controllers.shortlog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Shortlog controller for rhodecode
6 Shortlog controller for rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-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
27
28 from pylons import tmpl_context as c, request, url
28 from pylons import tmpl_context as c, request, url
29
29
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.helpers import RepoPage
32 from rhodecode.lib.helpers import RepoPage
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from rhodecode.lib.utils2 import safe_int
34
35
35 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
36
37
37
38
38 class ShortlogController(BaseRepoController):
39 class ShortlogController(BaseRepoController):
39
40
40 @LoginRequired()
41 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
43 'repository.admin')
43 def __before__(self):
44 def __before__(self):
44 super(ShortlogController, self).__before__()
45 super(ShortlogController, self).__before__()
45
46
46 def index(self, repo_name):
47 def index(self, repo_name):
47 p = int(request.params.get('page', 1))
48 p = safe_int(request.params.get('page', 1), 1)
48 size = int(request.params.get('size', 20))
49 size = safe_int(request.params.get('size', 20), 20)
49
50
50 def url_generator(**kw):
51 def url_generator(**kw):
51 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
52 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
52
53
53 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
54 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
54 items_per_page=size, url=url_generator)
55 items_per_page=size, url=url_generator)
55
56
56 if not c.repo_changesets:
57 if not c.repo_changesets:
57 return redirect(url('summary_home', repo_name=repo_name))
58 return redirect(url('summary_home', repo_name=repo_name))
58
59
59 c.shortlog_data = render('shortlog/shortlog_data.html')
60 c.shortlog_data = render('shortlog/shortlog_data.html')
60 if request.environ.get('HTTP_X_PARTIAL_XHR'):
61 if request.environ.get('HTTP_X_PARTIAL_XHR'):
61 return c.shortlog_data
62 return c.shortlog_data
62 r = render('shortlog/shortlog.html')
63 r = render('shortlog/shortlog.html')
63 return r
64 return r
@@ -1,466 +1,483 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 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 re
26 import re
27 import time
27 import time
28 import datetime
28 import datetime
29 from pylons.i18n.translation import _, ungettext
29 from pylons.i18n.translation import _, ungettext
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31
31
32
32
33 def __get_lem():
33 def __get_lem():
34 """
34 """
35 Get language extension map based on what's inside pygments lexers
35 Get language extension map based on what's inside pygments lexers
36 """
36 """
37 from pygments import lexers
37 from pygments import lexers
38 from string import lower
38 from string import lower
39 from collections import defaultdict
39 from collections import defaultdict
40
40
41 d = defaultdict(lambda: [])
41 d = defaultdict(lambda: [])
42
42
43 def __clean(s):
43 def __clean(s):
44 s = s.lstrip('*')
44 s = s.lstrip('*')
45 s = s.lstrip('.')
45 s = s.lstrip('.')
46
46
47 if s.find('[') != -1:
47 if s.find('[') != -1:
48 exts = []
48 exts = []
49 start, stop = s.find('['), s.find(']')
49 start, stop = s.find('['), s.find(']')
50
50
51 for suffix in s[start + 1:stop]:
51 for suffix in s[start + 1:stop]:
52 exts.append(s[:s.find('[')] + suffix)
52 exts.append(s[:s.find('[')] + suffix)
53 return map(lower, exts)
53 return map(lower, exts)
54 else:
54 else:
55 return map(lower, [s])
55 return map(lower, [s])
56
56
57 for lx, t in sorted(lexers.LEXERS.items()):
57 for lx, t in sorted(lexers.LEXERS.items()):
58 m = map(__clean, t[-2])
58 m = map(__clean, t[-2])
59 if m:
59 if m:
60 m = reduce(lambda x, y: x + y, m)
60 m = reduce(lambda x, y: x + y, m)
61 for ext in m:
61 for ext in m:
62 desc = lx.replace('Lexer', '')
62 desc = lx.replace('Lexer', '')
63 d[ext].append(desc)
63 d[ext].append(desc)
64
64
65 return dict(d)
65 return dict(d)
66
66
67 def str2bool(_str):
67 def str2bool(_str):
68 """
68 """
69 returs True/False value from given string, it tries to translate the
69 returs True/False value from given string, it tries to translate the
70 string into boolean
70 string into boolean
71
71
72 :param _str: string value to translate into boolean
72 :param _str: string value to translate into boolean
73 :rtype: boolean
73 :rtype: boolean
74 :returns: boolean from given string
74 :returns: boolean from given string
75 """
75 """
76 if _str is None:
76 if _str is None:
77 return False
77 return False
78 if _str in (True, False):
78 if _str in (True, False):
79 return _str
79 return _str
80 _str = str(_str).strip().lower()
80 _str = str(_str).strip().lower()
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82
82
83
83
84 def convert_line_endings(line, mode):
84 def convert_line_endings(line, mode):
85 """
85 """
86 Converts a given line "line end" accordingly to given mode
86 Converts a given line "line end" accordingly to given mode
87
87
88 Available modes are::
88 Available modes are::
89 0 - Unix
89 0 - Unix
90 1 - Mac
90 1 - Mac
91 2 - DOS
91 2 - DOS
92
92
93 :param line: given line to convert
93 :param line: given line to convert
94 :param mode: mode to convert to
94 :param mode: mode to convert to
95 :rtype: str
95 :rtype: str
96 :return: converted line according to mode
96 :return: converted line according to mode
97 """
97 """
98 from string import replace
98 from string import replace
99
99
100 if mode == 0:
100 if mode == 0:
101 line = replace(line, '\r\n', '\n')
101 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r', '\n')
102 line = replace(line, '\r', '\n')
103 elif mode == 1:
103 elif mode == 1:
104 line = replace(line, '\r\n', '\r')
104 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\n', '\r')
105 line = replace(line, '\n', '\r')
106 elif mode == 2:
106 elif mode == 2:
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 return line
108 return line
109
109
110
110
111 def detect_mode(line, default):
111 def detect_mode(line, default):
112 """
112 """
113 Detects line break for given line, if line break couldn't be found
113 Detects line break for given line, if line break couldn't be found
114 given default value is returned
114 given default value is returned
115
115
116 :param line: str line
116 :param line: str line
117 :param default: default
117 :param default: default
118 :rtype: int
118 :rtype: int
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 """
120 """
121 if line.endswith('\r\n'):
121 if line.endswith('\r\n'):
122 return 2
122 return 2
123 elif line.endswith('\n'):
123 elif line.endswith('\n'):
124 return 0
124 return 0
125 elif line.endswith('\r'):
125 elif line.endswith('\r'):
126 return 1
126 return 1
127 else:
127 else:
128 return default
128 return default
129
129
130
130
131 def generate_api_key(username, salt=None):
131 def generate_api_key(username, salt=None):
132 """
132 """
133 Generates unique API key for given username, if salt is not given
133 Generates unique API key for given username, if salt is not given
134 it'll be generated from some random string
134 it'll be generated from some random string
135
135
136 :param username: username as string
136 :param username: username as string
137 :param salt: salt to hash generate KEY
137 :param salt: salt to hash generate KEY
138 :rtype: str
138 :rtype: str
139 :returns: sha1 hash from username+salt
139 :returns: sha1 hash from username+salt
140 """
140 """
141 from tempfile import _RandomNameSequence
141 from tempfile import _RandomNameSequence
142 import hashlib
142 import hashlib
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(username + salt).hexdigest()
147 return hashlib.sha1(username + salt).hexdigest()
148
148
149
149
150 def safe_int(val, default=None):
151 """
152 Returns int() of val if val is not convertable to int use default
153 instead
154
155 :param val:
156 :param default:
157 """
158
159 try:
160 val = int(val)
161 except ValueError:
162 val = default
163
164 return val
165
166
150 def safe_unicode(str_, from_encoding=None):
167 def safe_unicode(str_, from_encoding=None):
151 """
168 """
152 safe unicode function. Does few trick to turn str_ into unicode
169 safe unicode function. Does few trick to turn str_ into unicode
153
170
154 In case of UnicodeDecode error we try to return it with encoding detected
171 In case of UnicodeDecode error we try to return it with encoding detected
155 by chardet library if it fails fallback to unicode with errors replaced
172 by chardet library if it fails fallback to unicode with errors replaced
156
173
157 :param str_: string to decode
174 :param str_: string to decode
158 :rtype: unicode
175 :rtype: unicode
159 :returns: unicode object
176 :returns: unicode object
160 """
177 """
161 if isinstance(str_, unicode):
178 if isinstance(str_, unicode):
162 return str_
179 return str_
163
180
164 if not from_encoding:
181 if not from_encoding:
165 import rhodecode
182 import rhodecode
166 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
183 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
167 from_encoding = DEFAULT_ENCODING
184 from_encoding = DEFAULT_ENCODING
168
185
169 try:
186 try:
170 return unicode(str_)
187 return unicode(str_)
171 except UnicodeDecodeError:
188 except UnicodeDecodeError:
172 pass
189 pass
173
190
174 try:
191 try:
175 return unicode(str_, from_encoding)
192 return unicode(str_, from_encoding)
176 except UnicodeDecodeError:
193 except UnicodeDecodeError:
177 pass
194 pass
178
195
179 try:
196 try:
180 import chardet
197 import chardet
181 encoding = chardet.detect(str_)['encoding']
198 encoding = chardet.detect(str_)['encoding']
182 if encoding is None:
199 if encoding is None:
183 raise Exception()
200 raise Exception()
184 return str_.decode(encoding)
201 return str_.decode(encoding)
185 except (ImportError, UnicodeDecodeError, Exception):
202 except (ImportError, UnicodeDecodeError, Exception):
186 return unicode(str_, from_encoding, 'replace')
203 return unicode(str_, from_encoding, 'replace')
187
204
188
205
189 def safe_str(unicode_, to_encoding=None):
206 def safe_str(unicode_, to_encoding=None):
190 """
207 """
191 safe str function. Does few trick to turn unicode_ into string
208 safe str function. Does few trick to turn unicode_ into string
192
209
193 In case of UnicodeEncodeError we try to return it with encoding detected
210 In case of UnicodeEncodeError we try to return it with encoding detected
194 by chardet library if it fails fallback to string with errors replaced
211 by chardet library if it fails fallback to string with errors replaced
195
212
196 :param unicode_: unicode to encode
213 :param unicode_: unicode to encode
197 :rtype: str
214 :rtype: str
198 :returns: str object
215 :returns: str object
199 """
216 """
200
217
201 # if it's not basestr cast to str
218 # if it's not basestr cast to str
202 if not isinstance(unicode_, basestring):
219 if not isinstance(unicode_, basestring):
203 return str(unicode_)
220 return str(unicode_)
204
221
205 if isinstance(unicode_, str):
222 if isinstance(unicode_, str):
206 return unicode_
223 return unicode_
207
224
208 if not to_encoding:
225 if not to_encoding:
209 import rhodecode
226 import rhodecode
210 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
227 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
211 to_encoding = DEFAULT_ENCODING
228 to_encoding = DEFAULT_ENCODING
212
229
213 try:
230 try:
214 return unicode_.encode(to_encoding)
231 return unicode_.encode(to_encoding)
215 except UnicodeEncodeError:
232 except UnicodeEncodeError:
216 pass
233 pass
217
234
218 try:
235 try:
219 import chardet
236 import chardet
220 encoding = chardet.detect(unicode_)['encoding']
237 encoding = chardet.detect(unicode_)['encoding']
221 if encoding is None:
238 if encoding is None:
222 raise UnicodeEncodeError()
239 raise UnicodeEncodeError()
223
240
224 return unicode_.encode(encoding)
241 return unicode_.encode(encoding)
225 except (ImportError, UnicodeEncodeError):
242 except (ImportError, UnicodeEncodeError):
226 return unicode_.encode(to_encoding, 'replace')
243 return unicode_.encode(to_encoding, 'replace')
227
244
228 return safe_str
245 return safe_str
229
246
230
247
231 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
248 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
232 """
249 """
233 Custom engine_from_config functions that makes sure we use NullPool for
250 Custom engine_from_config functions that makes sure we use NullPool for
234 file based sqlite databases. This prevents errors on sqlite. This only
251 file based sqlite databases. This prevents errors on sqlite. This only
235 applies to sqlalchemy versions < 0.7.0
252 applies to sqlalchemy versions < 0.7.0
236
253
237 """
254 """
238 import sqlalchemy
255 import sqlalchemy
239 from sqlalchemy import engine_from_config as efc
256 from sqlalchemy import engine_from_config as efc
240 import logging
257 import logging
241
258
242 if int(sqlalchemy.__version__.split('.')[1]) < 7:
259 if int(sqlalchemy.__version__.split('.')[1]) < 7:
243
260
244 # This solution should work for sqlalchemy < 0.7.0, and should use
261 # This solution should work for sqlalchemy < 0.7.0, and should use
245 # proxy=TimerProxy() for execution time profiling
262 # proxy=TimerProxy() for execution time profiling
246
263
247 from sqlalchemy.pool import NullPool
264 from sqlalchemy.pool import NullPool
248 url = configuration[prefix + 'url']
265 url = configuration[prefix + 'url']
249
266
250 if url.startswith('sqlite'):
267 if url.startswith('sqlite'):
251 kwargs.update({'poolclass': NullPool})
268 kwargs.update({'poolclass': NullPool})
252 return efc(configuration, prefix, **kwargs)
269 return efc(configuration, prefix, **kwargs)
253 else:
270 else:
254 import time
271 import time
255 from sqlalchemy import event
272 from sqlalchemy import event
256 from sqlalchemy.engine import Engine
273 from sqlalchemy.engine import Engine
257
274
258 log = logging.getLogger('sqlalchemy.engine')
275 log = logging.getLogger('sqlalchemy.engine')
259 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
276 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
260 engine = efc(configuration, prefix, **kwargs)
277 engine = efc(configuration, prefix, **kwargs)
261
278
262 def color_sql(sql):
279 def color_sql(sql):
263 COLOR_SEQ = "\033[1;%dm"
280 COLOR_SEQ = "\033[1;%dm"
264 COLOR_SQL = YELLOW
281 COLOR_SQL = YELLOW
265 normal = '\x1b[0m'
282 normal = '\x1b[0m'
266 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
283 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
267
284
268 if configuration['debug']:
285 if configuration['debug']:
269 #attach events only for debug configuration
286 #attach events only for debug configuration
270
287
271 def before_cursor_execute(conn, cursor, statement,
288 def before_cursor_execute(conn, cursor, statement,
272 parameters, context, executemany):
289 parameters, context, executemany):
273 context._query_start_time = time.time()
290 context._query_start_time = time.time()
274 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
291 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
275
292
276 def after_cursor_execute(conn, cursor, statement,
293 def after_cursor_execute(conn, cursor, statement,
277 parameters, context, executemany):
294 parameters, context, executemany):
278 total = time.time() - context._query_start_time
295 total = time.time() - context._query_start_time
279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
296 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
280
297
281 event.listen(engine, "before_cursor_execute",
298 event.listen(engine, "before_cursor_execute",
282 before_cursor_execute)
299 before_cursor_execute)
283 event.listen(engine, "after_cursor_execute",
300 event.listen(engine, "after_cursor_execute",
284 after_cursor_execute)
301 after_cursor_execute)
285
302
286 return engine
303 return engine
287
304
288
305
289 def age(prevdate):
306 def age(prevdate):
290 """
307 """
291 turns a datetime into an age string.
308 turns a datetime into an age string.
292
309
293 :param prevdate: datetime object
310 :param prevdate: datetime object
294 :rtype: unicode
311 :rtype: unicode
295 :returns: unicode words describing age
312 :returns: unicode words describing age
296 """
313 """
297
314
298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
315 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
299 deltas = {}
316 deltas = {}
300
317
301 # Get date parts deltas
318 # Get date parts deltas
302 now = datetime.datetime.now()
319 now = datetime.datetime.now()
303 for part in order:
320 for part in order:
304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
321 deltas[part] = getattr(now, part) - getattr(prevdate, part)
305
322
306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
323 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
307 # not 1 hour, -59 minutes and -59 seconds)
324 # not 1 hour, -59 minutes and -59 seconds)
308
325
309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
326 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
310 part = order[num]
327 part = order[num]
311 carry_part = order[num - 1]
328 carry_part = order[num - 1]
312
329
313 if deltas[part] < 0:
330 if deltas[part] < 0:
314 deltas[part] += length
331 deltas[part] += length
315 deltas[carry_part] -= 1
332 deltas[carry_part] -= 1
316
333
317 # Same thing for days except that the increment depends on the (variable)
334 # Same thing for days except that the increment depends on the (variable)
318 # number of days in the month
335 # number of days in the month
319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
336 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
320 if deltas['day'] < 0:
337 if deltas['day'] < 0:
321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
338 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
339 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
323 deltas['day'] += 29
340 deltas['day'] += 29
324 else:
341 else:
325 deltas['day'] += month_lengths[prevdate.month - 1]
342 deltas['day'] += month_lengths[prevdate.month - 1]
326
343
327 deltas['month'] -= 1
344 deltas['month'] -= 1
328
345
329 if deltas['month'] < 0:
346 if deltas['month'] < 0:
330 deltas['month'] += 12
347 deltas['month'] += 12
331 deltas['year'] -= 1
348 deltas['year'] -= 1
332
349
333 # Format the result
350 # Format the result
334 fmt_funcs = {
351 fmt_funcs = {
335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
352 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
353 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
354 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
355 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
356 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
357 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
341 }
358 }
342
359
343 for i, part in enumerate(order):
360 for i, part in enumerate(order):
344 value = deltas[part]
361 value = deltas[part]
345 if value == 0:
362 if value == 0:
346 continue
363 continue
347
364
348 if i < 5:
365 if i < 5:
349 sub_part = order[i + 1]
366 sub_part = order[i + 1]
350 sub_value = deltas[sub_part]
367 sub_value = deltas[sub_part]
351 else:
368 else:
352 sub_value = 0
369 sub_value = 0
353
370
354 if sub_value == 0:
371 if sub_value == 0:
355 return _(u'%s ago') % fmt_funcs[part](value)
372 return _(u'%s ago') % fmt_funcs[part](value)
356
373
357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
374 return _(u'%s and %s ago') % (fmt_funcs[part](value),
358 fmt_funcs[sub_part](sub_value))
375 fmt_funcs[sub_part](sub_value))
359
376
360 return _(u'just now')
377 return _(u'just now')
361
378
362
379
363 def uri_filter(uri):
380 def uri_filter(uri):
364 """
381 """
365 Removes user:password from given url string
382 Removes user:password from given url string
366
383
367 :param uri:
384 :param uri:
368 :rtype: unicode
385 :rtype: unicode
369 :returns: filtered list of strings
386 :returns: filtered list of strings
370 """
387 """
371 if not uri:
388 if not uri:
372 return ''
389 return ''
373
390
374 proto = ''
391 proto = ''
375
392
376 for pat in ('https://', 'http://'):
393 for pat in ('https://', 'http://'):
377 if uri.startswith(pat):
394 if uri.startswith(pat):
378 uri = uri[len(pat):]
395 uri = uri[len(pat):]
379 proto = pat
396 proto = pat
380 break
397 break
381
398
382 # remove passwords and username
399 # remove passwords and username
383 uri = uri[uri.find('@') + 1:]
400 uri = uri[uri.find('@') + 1:]
384
401
385 # get the port
402 # get the port
386 cred_pos = uri.find(':')
403 cred_pos = uri.find(':')
387 if cred_pos == -1:
404 if cred_pos == -1:
388 host, port = uri, None
405 host, port = uri, None
389 else:
406 else:
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
407 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391
408
392 return filter(None, [proto, host, port])
409 return filter(None, [proto, host, port])
393
410
394
411
395 def credentials_filter(uri):
412 def credentials_filter(uri):
396 """
413 """
397 Returns a url with removed credentials
414 Returns a url with removed credentials
398
415
399 :param uri:
416 :param uri:
400 """
417 """
401
418
402 uri = uri_filter(uri)
419 uri = uri_filter(uri)
403 #check if we have port
420 #check if we have port
404 if len(uri) > 2 and uri[2]:
421 if len(uri) > 2 and uri[2]:
405 uri[2] = ':' + uri[2]
422 uri[2] = ':' + uri[2]
406
423
407 return ''.join(uri)
424 return ''.join(uri)
408
425
409
426
410 def get_changeset_safe(repo, rev):
427 def get_changeset_safe(repo, rev):
411 """
428 """
412 Safe version of get_changeset if this changeset doesn't exists for a
429 Safe version of get_changeset if this changeset doesn't exists for a
413 repo it returns a Dummy one instead
430 repo it returns a Dummy one instead
414
431
415 :param repo:
432 :param repo:
416 :param rev:
433 :param rev:
417 """
434 """
418 from rhodecode.lib.vcs.backends.base import BaseRepository
435 from rhodecode.lib.vcs.backends.base import BaseRepository
419 from rhodecode.lib.vcs.exceptions import RepositoryError
436 from rhodecode.lib.vcs.exceptions import RepositoryError
420 from rhodecode.lib.vcs.backends.base import EmptyChangeset
437 from rhodecode.lib.vcs.backends.base import EmptyChangeset
421 if not isinstance(repo, BaseRepository):
438 if not isinstance(repo, BaseRepository):
422 raise Exception('You must pass an Repository '
439 raise Exception('You must pass an Repository '
423 'object as first argument got %s', type(repo))
440 'object as first argument got %s', type(repo))
424
441
425 try:
442 try:
426 cs = repo.get_changeset(rev)
443 cs = repo.get_changeset(rev)
427 except RepositoryError:
444 except RepositoryError:
428 cs = EmptyChangeset(requested_revision=rev)
445 cs = EmptyChangeset(requested_revision=rev)
429 return cs
446 return cs
430
447
431
448
432 def datetime_to_time(dt):
449 def datetime_to_time(dt):
433 if dt:
450 if dt:
434 return time.mktime(dt.timetuple())
451 return time.mktime(dt.timetuple())
435
452
436
453
437 def time_to_datetime(tm):
454 def time_to_datetime(tm):
438 if tm:
455 if tm:
439 if isinstance(tm, basestring):
456 if isinstance(tm, basestring):
440 try:
457 try:
441 tm = float(tm)
458 tm = float(tm)
442 except ValueError:
459 except ValueError:
443 return
460 return
444 return datetime.datetime.fromtimestamp(tm)
461 return datetime.datetime.fromtimestamp(tm)
445
462
446 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
463 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
447
464
448
465
449 def extract_mentioned_users(s):
466 def extract_mentioned_users(s):
450 """
467 """
451 Returns unique usernames from given string s that have @mention
468 Returns unique usernames from given string s that have @mention
452
469
453 :param s: string to get mentions
470 :param s: string to get mentions
454 """
471 """
455 usrs = set()
472 usrs = set()
456 for username in re.findall(MENTIONS_REGEX, s):
473 for username in re.findall(MENTIONS_REGEX, s):
457 usrs.add(username)
474 usrs.add(username)
458
475
459 return sorted(list(usrs), key=lambda k: k.lower())
476 return sorted(list(usrs), key=lambda k: k.lower())
460
477
461
478
462 class AttributeDict(dict):
479 class AttributeDict(dict):
463 def __getattr__(self, attr):
480 def __getattr__(self, attr):
464 return self.get(attr, None)
481 return self.get(attr, None)
465 __setattr__ = dict.__setitem__
482 __setattr__ = dict.__setitem__
466 __delattr__ = dict.__delitem__
483 __delattr__ = dict.__delitem__
General Comments 0
You need to be logged in to leave comments. Login now