##// END OF EJS Templates
usage of request.GET is now more consistent
marcink -
r3748:9d743ca9 beta
parent child Browse files
Show More
@@ -1,149 +1,149 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, url
28 from pylons import request, tmpl_context as c, url
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 whoosh.qparser.default import QueryParser
31 from whoosh.qparser.default import QueryParser
32 from whoosh import query
32 from whoosh import query
33 from sqlalchemy.sql.expression import or_, and_, func
33 from sqlalchemy.sql.expression import or_, and_, func
34
34
35 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
35 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.model.db import UserLog, User
37 from rhodecode.model.db import UserLog, User
38 from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix
38 from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix
39 from rhodecode.lib.indexers import JOURNAL_SCHEMA
39 from rhodecode.lib.indexers import JOURNAL_SCHEMA
40 from whoosh.qparser.dateparse import DateParserPlugin
40 from whoosh.qparser.dateparse import DateParserPlugin
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 def _journal_filter(user_log, search_term):
46 def _journal_filter(user_log, search_term):
47 """
47 """
48 Filters sqlalchemy user_log based on search_term with whoosh Query language
48 Filters sqlalchemy user_log based on search_term with whoosh Query language
49 http://packages.python.org/Whoosh/querylang.html
49 http://packages.python.org/Whoosh/querylang.html
50
50
51 :param user_log:
51 :param user_log:
52 :param search_term:
52 :param search_term:
53 """
53 """
54 log.debug('Initial search term: %r' % search_term)
54 log.debug('Initial search term: %r' % search_term)
55 qry = None
55 qry = None
56 if search_term:
56 if search_term:
57 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
57 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
58 qp.add_plugin(DateParserPlugin())
58 qp.add_plugin(DateParserPlugin())
59 qry = qp.parse(unicode(search_term))
59 qry = qp.parse(unicode(search_term))
60 log.debug('Filtering using parsed query %r' % qry)
60 log.debug('Filtering using parsed query %r' % qry)
61
61
62 def wildcard_handler(col, wc_term):
62 def wildcard_handler(col, wc_term):
63 if wc_term.startswith('*') and not wc_term.endswith('*'):
63 if wc_term.startswith('*') and not wc_term.endswith('*'):
64 #postfix == endswith
64 #postfix == endswith
65 wc_term = remove_prefix(wc_term, prefix='*')
65 wc_term = remove_prefix(wc_term, prefix='*')
66 return func.lower(col).endswith(wc_term)
66 return func.lower(col).endswith(wc_term)
67 elif wc_term.startswith('*') and wc_term.endswith('*'):
67 elif wc_term.startswith('*') and wc_term.endswith('*'):
68 #wildcard == ilike
68 #wildcard == ilike
69 wc_term = remove_prefix(wc_term, prefix='*')
69 wc_term = remove_prefix(wc_term, prefix='*')
70 wc_term = remove_suffix(wc_term, suffix='*')
70 wc_term = remove_suffix(wc_term, suffix='*')
71 return func.lower(col).contains(wc_term)
71 return func.lower(col).contains(wc_term)
72
72
73 def get_filterion(field, val, term):
73 def get_filterion(field, val, term):
74
74
75 if field == 'repository':
75 if field == 'repository':
76 field = getattr(UserLog, 'repository_name')
76 field = getattr(UserLog, 'repository_name')
77 elif field == 'ip':
77 elif field == 'ip':
78 field = getattr(UserLog, 'user_ip')
78 field = getattr(UserLog, 'user_ip')
79 elif field == 'date':
79 elif field == 'date':
80 field = getattr(UserLog, 'action_date')
80 field = getattr(UserLog, 'action_date')
81 elif field == 'username':
81 elif field == 'username':
82 field = getattr(UserLog, 'username')
82 field = getattr(UserLog, 'username')
83 else:
83 else:
84 field = getattr(UserLog, field)
84 field = getattr(UserLog, field)
85 log.debug('filter field: %s val=>%s' % (field, val))
85 log.debug('filter field: %s val=>%s' % (field, val))
86
86
87 #sql filtering
87 #sql filtering
88 if isinstance(term, query.Wildcard):
88 if isinstance(term, query.Wildcard):
89 return wildcard_handler(field, val)
89 return wildcard_handler(field, val)
90 elif isinstance(term, query.Prefix):
90 elif isinstance(term, query.Prefix):
91 return func.lower(field).startswith(func.lower(val))
91 return func.lower(field).startswith(func.lower(val))
92 elif isinstance(term, query.DateRange):
92 elif isinstance(term, query.DateRange):
93 return and_(field >= val[0], field <= val[1])
93 return and_(field >= val[0], field <= val[1])
94 return func.lower(field) == func.lower(val)
94 return func.lower(field) == func.lower(val)
95
95
96 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
96 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
97 query.DateRange)):
97 query.DateRange)):
98 if not isinstance(qry, query.And):
98 if not isinstance(qry, query.And):
99 qry = [qry]
99 qry = [qry]
100 for term in qry:
100 for term in qry:
101 field = term.fieldname
101 field = term.fieldname
102 val = (term.text if not isinstance(term, query.DateRange)
102 val = (term.text if not isinstance(term, query.DateRange)
103 else [term.startdate, term.enddate])
103 else [term.startdate, term.enddate])
104 user_log = user_log.filter(get_filterion(field, val, term))
104 user_log = user_log.filter(get_filterion(field, val, term))
105 elif isinstance(qry, query.Or):
105 elif isinstance(qry, query.Or):
106 filters = []
106 filters = []
107 for term in qry:
107 for term in qry:
108 field = term.fieldname
108 field = term.fieldname
109 val = (term.text if not isinstance(term, query.DateRange)
109 val = (term.text if not isinstance(term, query.DateRange)
110 else [term.startdate, term.enddate])
110 else [term.startdate, term.enddate])
111 filters.append(get_filterion(field, val, term))
111 filters.append(get_filterion(field, val, term))
112 user_log = user_log.filter(or_(*filters))
112 user_log = user_log.filter(or_(*filters))
113
113
114 return user_log
114 return user_log
115
115
116
116
117 class AdminController(BaseController):
117 class AdminController(BaseController):
118
118
119 @LoginRequired()
119 @LoginRequired()
120 def __before__(self):
120 def __before__(self):
121 super(AdminController, self).__before__()
121 super(AdminController, self).__before__()
122
122
123 @HasPermissionAllDecorator('hg.admin')
123 @HasPermissionAllDecorator('hg.admin')
124 def index(self):
124 def index(self):
125 users_log = UserLog.query()\
125 users_log = UserLog.query()\
126 .options(joinedload(UserLog.user))\
126 .options(joinedload(UserLog.user))\
127 .options(joinedload(UserLog.repository))
127 .options(joinedload(UserLog.repository))
128
128
129 #FILTERING
129 #FILTERING
130 c.search_term = request.GET.get('filter')
130 c.search_term = request.GET.get('filter')
131 try:
131 try:
132 users_log = _journal_filter(users_log, c.search_term)
132 users_log = _journal_filter(users_log, c.search_term)
133 except Exception:
133 except Exception:
134 # we want this to crash for now
134 # we want this to crash for now
135 raise
135 raise
136
136
137 users_log = users_log.order_by(UserLog.action_date.desc())
137 users_log = users_log.order_by(UserLog.action_date.desc())
138
138
139 p = safe_int(request.params.get('page', 1), 1)
139 p = safe_int(request.GET.get('page', 1), 1)
140
140
141 def url_generator(**kw):
141 def url_generator(**kw):
142 return url.current(filter=c.search_term, **kw)
142 return url.current(filter=c.search_term, **kw)
143
143
144 c.users_log = Page(users_log, page=p, items_per_page=10, url=url_generator)
144 c.users_log = Page(users_log, page=p, items_per_page=10, url=url_generator)
145 c.log_data = render('admin/admin_log.html')
145 c.log_data = render('admin/admin_log.html')
146
146
147 if request.environ.get('HTTP_X_PARTIAL_XHR'):
147 if request.environ.get('HTTP_X_PARTIAL_XHR'):
148 return c.log_data
148 return c.log_data
149 return render('admin/admin.html')
149 return render('admin/admin.html')
@@ -1,173 +1,173 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, abort
31 from pylons.controllers.util import redirect, abort
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 from rhodecode.lib.utils2 import safe_int
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class NotificationsController(BaseController):
48 class NotificationsController(BaseController):
49 """REST Controller styled on the Atom Publishing Protocol"""
49 """REST Controller styled on the Atom Publishing Protocol"""
50 # To properly map this controller, ensure your config/routing.py
50 # To properly map this controller, ensure your config/routing.py
51 # file has a resource setup:
51 # file has a resource setup:
52 # map.resource('notification', 'notifications', controller='_admin/notifications',
52 # map.resource('notification', 'notifications', controller='_admin/notifications',
53 # path_prefix='/_admin', name_prefix='_admin_')
53 # path_prefix='/_admin', name_prefix='_admin_')
54
54
55 @LoginRequired()
55 @LoginRequired()
56 @NotAnonymous()
56 @NotAnonymous()
57 def __before__(self):
57 def __before__(self):
58 super(NotificationsController, self).__before__()
58 super(NotificationsController, self).__before__()
59
59
60 def index(self, format='html'):
60 def index(self, format='html'):
61 """GET /_admin/notifications: All items in the collection"""
61 """GET /_admin/notifications: All items in the collection"""
62 # url('notifications')
62 # url('notifications')
63 c.user = self.rhodecode_user
63 c.user = self.rhodecode_user
64 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
64 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
65 filter_=request.GET.getall('type'))
65 filter_=request.GET.getall('type'))
66
66
67 p = safe_int(request.params.get('page', 1), 1)
67 p = safe_int(request.GET.get('page', 1), 1)
68 c.notifications = Page(notif, page=p, items_per_page=10)
68 c.notifications = Page(notif, page=p, items_per_page=10)
69 c.pull_request_type = Notification.TYPE_PULL_REQUEST
69 c.pull_request_type = Notification.TYPE_PULL_REQUEST
70 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
70 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
71 Notification.TYPE_PULL_REQUEST_COMMENT]
71 Notification.TYPE_PULL_REQUEST_COMMENT]
72
72
73 _current_filter = request.GET.getall('type')
73 _current_filter = request.GET.getall('type')
74 c.current_filter = 'all'
74 c.current_filter = 'all'
75 if _current_filter == [c.pull_request_type]:
75 if _current_filter == [c.pull_request_type]:
76 c.current_filter = 'pull_request'
76 c.current_filter = 'pull_request'
77 elif _current_filter == c.comment_type:
77 elif _current_filter == c.comment_type:
78 c.current_filter = 'comment'
78 c.current_filter = 'comment'
79
79
80 return render('admin/notifications/notifications.html')
80 return render('admin/notifications/notifications.html')
81
81
82 def mark_all_read(self):
82 def mark_all_read(self):
83 if request.environ.get('HTTP_X_PARTIAL_XHR'):
83 if request.environ.get('HTTP_X_PARTIAL_XHR'):
84 nm = NotificationModel()
84 nm = NotificationModel()
85 # mark all read
85 # mark all read
86 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
86 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
87 filter_=request.GET.getall('type'))
87 filter_=request.GET.getall('type'))
88 Session().commit()
88 Session().commit()
89 c.user = self.rhodecode_user
89 c.user = self.rhodecode_user
90 notif = nm.get_for_user(self.rhodecode_user.user_id,
90 notif = nm.get_for_user(self.rhodecode_user.user_id,
91 filter_=request.GET.getall('type'))
91 filter_=request.GET.getall('type'))
92 c.notifications = Page(notif, page=1, items_per_page=10)
92 c.notifications = Page(notif, page=1, items_per_page=10)
93 return render('admin/notifications/notifications_data.html')
93 return render('admin/notifications/notifications_data.html')
94
94
95 def create(self):
95 def create(self):
96 """POST /_admin/notifications: Create a new item"""
96 """POST /_admin/notifications: Create a new item"""
97 # url('notifications')
97 # url('notifications')
98
98
99 def new(self, format='html'):
99 def new(self, format='html'):
100 """GET /_admin/notifications/new: Form to create a new item"""
100 """GET /_admin/notifications/new: Form to create a new item"""
101 # url('new_notification')
101 # url('new_notification')
102
102
103 def update(self, notification_id):
103 def update(self, notification_id):
104 """PUT /_admin/notifications/id: Update an existing item"""
104 """PUT /_admin/notifications/id: Update an existing item"""
105 # Forms posted to this method should contain a hidden field:
105 # Forms posted to this method should contain a hidden field:
106 # <input type="hidden" name="_method" value="PUT" />
106 # <input type="hidden" name="_method" value="PUT" />
107 # Or using helpers:
107 # Or using helpers:
108 # h.form(url('notification', notification_id=ID),
108 # h.form(url('notification', notification_id=ID),
109 # method='put')
109 # method='put')
110 # url('notification', notification_id=ID)
110 # url('notification', notification_id=ID)
111 try:
111 try:
112 no = Notification.get(notification_id)
112 no = Notification.get(notification_id)
113 owner = all(un.user.user_id == c.rhodecode_user.user_id
113 owner = all(un.user.user_id == c.rhodecode_user.user_id
114 for un in no.notifications_to_users)
114 for un in no.notifications_to_users)
115 if h.HasPermissionAny('hg.admin')() or owner:
115 if h.HasPermissionAny('hg.admin')() or owner:
116 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
116 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
117 Session().commit()
117 Session().commit()
118 return 'ok'
118 return 'ok'
119 except Exception:
119 except Exception:
120 Session().rollback()
120 Session().rollback()
121 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
122 return 'fail'
122 return 'fail'
123
123
124 def delete(self, notification_id):
124 def delete(self, notification_id):
125 """DELETE /_admin/notifications/id: Delete an existing item"""
125 """DELETE /_admin/notifications/id: Delete an existing item"""
126 # Forms posted to this method should contain a hidden field:
126 # Forms posted to this method should contain a hidden field:
127 # <input type="hidden" name="_method" value="DELETE" />
127 # <input type="hidden" name="_method" value="DELETE" />
128 # Or using helpers:
128 # Or using helpers:
129 # h.form(url('notification', notification_id=ID),
129 # h.form(url('notification', notification_id=ID),
130 # method='delete')
130 # method='delete')
131 # url('notification', notification_id=ID)
131 # url('notification', notification_id=ID)
132
132
133 try:
133 try:
134 no = Notification.get(notification_id)
134 no = Notification.get(notification_id)
135 owner = all(un.user.user_id == c.rhodecode_user.user_id
135 owner = all(un.user.user_id == c.rhodecode_user.user_id
136 for un in no.notifications_to_users)
136 for un in no.notifications_to_users)
137 if h.HasPermissionAny('hg.admin')() or owner:
137 if h.HasPermissionAny('hg.admin')() or owner:
138 NotificationModel().delete(c.rhodecode_user.user_id, no)
138 NotificationModel().delete(c.rhodecode_user.user_id, no)
139 Session().commit()
139 Session().commit()
140 return 'ok'
140 return 'ok'
141 except Exception:
141 except Exception:
142 Session().rollback()
142 Session().rollback()
143 log.error(traceback.format_exc())
143 log.error(traceback.format_exc())
144 return 'fail'
144 return 'fail'
145
145
146 def show(self, notification_id, format='html'):
146 def show(self, notification_id, format='html'):
147 """GET /_admin/notifications/id: Show a specific item"""
147 """GET /_admin/notifications/id: Show a specific item"""
148 # url('notification', notification_id=ID)
148 # url('notification', notification_id=ID)
149 c.user = self.rhodecode_user
149 c.user = self.rhodecode_user
150 no = Notification.get(notification_id)
150 no = Notification.get(notification_id)
151
151
152 owner = any(un.user.user_id == c.rhodecode_user.user_id
152 owner = any(un.user.user_id == c.rhodecode_user.user_id
153 for un in no.notifications_to_users)
153 for un in no.notifications_to_users)
154
154
155 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
155 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
156 unotification = NotificationModel()\
156 unotification = NotificationModel()\
157 .get_user_notification(c.user.user_id, no)
157 .get_user_notification(c.user.user_id, no)
158
158
159 # if this association to user is not valid, we don't want to show
159 # if this association to user is not valid, we don't want to show
160 # this message
160 # this message
161 if unotification:
161 if unotification:
162 if not unotification.read:
162 if not unotification.read:
163 unotification.mark_as_read()
163 unotification.mark_as_read()
164 Session().commit()
164 Session().commit()
165 c.notification = no
165 c.notification = no
166
166
167 return render('admin/notifications/show_notification.html')
167 return render('admin/notifications/show_notification.html')
168
168
169 return abort(403)
169 return abort(403)
170
170
171 def edit(self, notification_id, format='html'):
171 def edit(self, notification_id, format='html'):
172 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
172 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
173 # url('edit_notification', notification_id=ID)
173 # url('edit_notification', notification_id=ID)
@@ -1,123 +1,119 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 from rhodecode.lib.utils2 import safe_int
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class ChangelogController(BaseRepoController):
45 class ChangelogController(BaseRepoController):
46
46
47 @LoginRequired()
47 @LoginRequired()
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 'repository.admin')
49 'repository.admin')
50 def __before__(self):
50 def __before__(self):
51 super(ChangelogController, self).__before__()
51 super(ChangelogController, self).__before__()
52 c.affected_files_cut_off = 60
52 c.affected_files_cut_off = 60
53
53
54 def index(self):
54 def index(self):
55 limit = 100
55 limit = 100
56 default = 20
56 default = 20
57 if request.params.get('size'):
57 if request.GET.get('size'):
58 try:
58 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
59 int_size = int(request.params.get('size'))
60 except ValueError:
61 int_size = default
62 c.size = max(min(int_size, limit), 1)
63 session['changelog_size'] = c.size
59 session['changelog_size'] = c.size
64 session.save()
60 session.save()
65 else:
61 else:
66 c.size = int(session.get('changelog_size', default))
62 c.size = int(session.get('changelog_size', default))
67 # min size must be 1
63 # min size must be 1
68 c.size = max(c.size, 1)
64 c.size = max(c.size, 1)
69 p = safe_int(request.params.get('page', 1), 1)
65 p = safe_int(request.GET.get('page', 1), 1)
70 branch_name = request.params.get('branch', None)
66 branch_name = request.GET.get('branch', None)
71 try:
67 try:
72 collection = c.rhodecode_repo.get_changesets(start=0,
68 collection = c.rhodecode_repo.get_changesets(start=0,
73 branch_name=branch_name)
69 branch_name=branch_name)
74 c.total_cs = len(collection)
70 c.total_cs = len(collection)
75
71
76 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
72 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
77 items_per_page=c.size, branch=branch_name)
73 items_per_page=c.size, branch=branch_name)
78 collection = list(c.pagination)
74 collection = list(c.pagination)
79 page_revisions = [x.raw_id for x in c.pagination]
75 page_revisions = [x.raw_id for x in c.pagination]
80 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
76 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
81 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
77 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
82 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
78 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
83 log.error(traceback.format_exc())
79 log.error(traceback.format_exc())
84 h.flash(str(e), category='error')
80 h.flash(str(e), category='error')
85 return redirect(url('changelog_home', repo_name=c.repo_name))
81 return redirect(url('changelog_home', repo_name=c.repo_name))
86
82
87 c.branch_name = branch_name
83 c.branch_name = branch_name
88 c.branch_filters = [('', _('All Branches'))] + \
84 c.branch_filters = [('', _('All Branches'))] + \
89 [(k, k) for k in c.rhodecode_repo.branches.keys()]
85 [(k, k) for k in c.rhodecode_repo.branches.keys()]
90
86
91 self._graph(c.rhodecode_repo, [x.revision for x in c.pagination],
87 self._graph(c.rhodecode_repo, [x.revision for x in c.pagination],
92 c.total_cs, c.size, p)
88 c.total_cs, c.size, p)
93
89
94 return render('changelog/changelog.html')
90 return render('changelog/changelog.html')
95
91
96 def changelog_details(self, cs):
92 def changelog_details(self, cs):
97 if request.environ.get('HTTP_X_PARTIAL_XHR'):
93 if request.environ.get('HTTP_X_PARTIAL_XHR'):
98 c.cs = c.rhodecode_repo.get_changeset(cs)
94 c.cs = c.rhodecode_repo.get_changeset(cs)
99 return render('changelog/changelog_details.html')
95 return render('changelog/changelog_details.html')
100
96
101 def _graph(self, repo, revs_int, repo_size, size, p):
97 def _graph(self, repo, revs_int, repo_size, size, p):
102 """
98 """
103 Generates a DAG graph for repo
99 Generates a DAG graph for repo
104
100
105 :param repo:
101 :param repo:
106 :param revs_int:
102 :param revs_int:
107 :param repo_size:
103 :param repo_size:
108 :param size:
104 :param size:
109 :param p:
105 :param p:
110 """
106 """
111 if not revs_int:
107 if not revs_int:
112 c.jsdata = json.dumps([])
108 c.jsdata = json.dumps([])
113 return
109 return
114
110
115 data = []
111 data = []
116 revs = revs_int
112 revs = revs_int
117
113
118 dag = _dagwalker(repo, revs, repo.alias)
114 dag = _dagwalker(repo, revs, repo.alias)
119 dag = _colored(dag)
115 dag = _colored(dag)
120 for (id, type, ctx, vtx, edges) in dag:
116 for (id, type, ctx, vtx, edges) in dag:
121 data.append(['', vtx, edges])
117 data.append(['', vtx, edges])
122
118
123 c.jsdata = json.dumps(data)
119 c.jsdata = json.dumps(data)
@@ -1,58 +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 from rhodecode.lib.utils2 import safe_int
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class FollowersController(BaseRepoController):
38 class FollowersController(BaseRepoController):
39
39
40 @LoginRequired()
40 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
42 'repository.admin')
43 def __before__(self):
43 def __before__(self):
44 super(FollowersController, self).__before__()
44 super(FollowersController, self).__before__()
45
45
46 def followers(self, repo_name):
46 def followers(self, repo_name):
47 p = safe_int(request.params.get('page', 1), 1)
47 p = safe_int(request.GET.get('page', 1), 1)
48 repo_id = c.rhodecode_db_repo.repo_id
48 repo_id = c.rhodecode_db_repo.repo_id
49 d = UserFollowing.get_repo_followers(repo_id)\
49 d = UserFollowing.get_repo_followers(repo_id)\
50 .order_by(UserFollowing.follows_from)
50 .order_by(UserFollowing.follows_from)
51 c.followers_pager = Page(d, page=p, items_per_page=20)
51 c.followers_pager = Page(d, page=p, items_per_page=20)
52
52
53 c.followers_data = render('/followers/followers_data.html')
53 c.followers_data = render('/followers/followers_data.html')
54
54
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
56 return c.followers_data
56 return c.followers_data
57
57
58 return render('/followers/followers.html')
58 return render('/followers/followers.html')
@@ -1,191 +1,191 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 RhodeCodeUi
42 RhodeCodeUi
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.forms import RepoForkForm
45 from rhodecode.model.scm import ScmModel, RepoGroupList
45 from rhodecode.model.scm import ScmModel, RepoGroupList
46 from rhodecode.lib.utils2 import safe_int
46 from rhodecode.lib.utils2 import safe_int
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class ForksController(BaseRepoController):
51 class ForksController(BaseRepoController):
52
52
53 @LoginRequired()
53 @LoginRequired()
54 def __before__(self):
54 def __before__(self):
55 super(ForksController, self).__before__()
55 super(ForksController, self).__before__()
56
56
57 def __load_defaults(self):
57 def __load_defaults(self):
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
58 acl_groups = RepoGroupList(RepoGroup.query().all(),
59 perm_set=['group.write', 'group.admin'])
59 perm_set=['group.write', 'group.admin'])
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
60 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
61 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
61 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
62 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
62 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
63 c.landing_revs_choices = choices
63 c.landing_revs_choices = choices
64 c.can_update = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active
64 c.can_update = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active
65
65
66 def __load_data(self, repo_name=None):
66 def __load_data(self, repo_name=None):
67 """
67 """
68 Load defaults settings for edit, and update
68 Load defaults settings for edit, and update
69
69
70 :param repo_name:
70 :param repo_name:
71 """
71 """
72 self.__load_defaults()
72 self.__load_defaults()
73
73
74 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
74 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
75 repo = db_repo.scm_instance
75 repo = db_repo.scm_instance
76
76
77 if c.repo_info is None:
77 if c.repo_info is None:
78 h.not_mapped_error(repo_name)
78 h.not_mapped_error(repo_name)
79 return redirect(url('repos'))
79 return redirect(url('repos'))
80
80
81 c.default_user_id = User.get_default_user().user_id
81 c.default_user_id = User.get_default_user().user_id
82 c.in_public_journal = UserFollowing.query()\
82 c.in_public_journal = UserFollowing.query()\
83 .filter(UserFollowing.user_id == c.default_user_id)\
83 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85
85
86 if c.repo_info.stats:
86 if c.repo_info.stats:
87 last_rev = c.repo_info.stats.stat_on_revision+1
87 last_rev = c.repo_info.stats.stat_on_revision+1
88 else:
88 else:
89 last_rev = 0
89 last_rev = 0
90 c.stats_revision = last_rev
90 c.stats_revision = last_rev
91
91
92 c.repo_last_rev = repo.count() if repo.revisions else 0
92 c.repo_last_rev = repo.count() if repo.revisions else 0
93
93
94 if last_rev == 0 or c.repo_last_rev == 0:
94 if last_rev == 0 or c.repo_last_rev == 0:
95 c.stats_percentage = 0
95 c.stats_percentage = 0
96 else:
96 else:
97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.repo_last_rev) * 100)
98 c.repo_last_rev) * 100)
99
99
100 defaults = RepoModel()._get_defaults(repo_name)
100 defaults = RepoModel()._get_defaults(repo_name)
101 # alter the description to indicate a fork
101 # alter the description to indicate a fork
102 defaults['description'] = ('fork of repository: %s \n%s'
102 defaults['description'] = ('fork of repository: %s \n%s'
103 % (defaults['repo_name'],
103 % (defaults['repo_name'],
104 defaults['description']))
104 defaults['description']))
105 # add suffix to fork
105 # add suffix to fork
106 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
106 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
107
107
108 return defaults
108 return defaults
109
109
110 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
110 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
111 'repository.admin')
111 'repository.admin')
112 def forks(self, repo_name):
112 def forks(self, repo_name):
113 p = safe_int(request.params.get('page', 1), 1)
113 p = safe_int(request.GET.get('page', 1), 1)
114 repo_id = c.rhodecode_db_repo.repo_id
114 repo_id = c.rhodecode_db_repo.repo_id
115 d = []
115 d = []
116 for r in Repository.get_repo_forks(repo_id):
116 for r in Repository.get_repo_forks(repo_id):
117 if not HasRepoPermissionAny(
117 if not HasRepoPermissionAny(
118 'repository.read', 'repository.write', 'repository.admin'
118 'repository.read', 'repository.write', 'repository.admin'
119 )(r.repo_name, 'get forks check'):
119 )(r.repo_name, 'get forks check'):
120 continue
120 continue
121 d.append(r)
121 d.append(r)
122 c.forks_pager = Page(d, page=p, items_per_page=20)
122 c.forks_pager = Page(d, page=p, items_per_page=20)
123
123
124 c.forks_data = render('/forks/forks_data.html')
124 c.forks_data = render('/forks/forks_data.html')
125
125
126 if request.environ.get('HTTP_X_PARTIAL_XHR'):
126 if request.environ.get('HTTP_X_PARTIAL_XHR'):
127 return c.forks_data
127 return c.forks_data
128
128
129 return render('/forks/forks.html')
129 return render('/forks/forks.html')
130
130
131 @NotAnonymous()
131 @NotAnonymous()
132 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
132 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
133 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
133 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
134 'repository.admin')
134 'repository.admin')
135 def fork(self, repo_name):
135 def fork(self, repo_name):
136 c.repo_info = Repository.get_by_repo_name(repo_name)
136 c.repo_info = Repository.get_by_repo_name(repo_name)
137 if not c.repo_info:
137 if not c.repo_info:
138 h.not_mapped_error(repo_name)
138 h.not_mapped_error(repo_name)
139 return redirect(url('home'))
139 return redirect(url('home'))
140
140
141 defaults = self.__load_data(repo_name)
141 defaults = self.__load_data(repo_name)
142
142
143 return htmlfill.render(
143 return htmlfill.render(
144 render('forks/fork.html'),
144 render('forks/fork.html'),
145 defaults=defaults,
145 defaults=defaults,
146 encoding="UTF-8",
146 encoding="UTF-8",
147 force_defaults=False
147 force_defaults=False
148 )
148 )
149
149
150 @NotAnonymous()
150 @NotAnonymous()
151 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
151 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
152 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 'repository.admin')
153 'repository.admin')
154 def fork_create(self, repo_name):
154 def fork_create(self, repo_name):
155 self.__load_defaults()
155 self.__load_defaults()
156 c.repo_info = Repository.get_by_repo_name(repo_name)
156 c.repo_info = Repository.get_by_repo_name(repo_name)
157 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
157 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 repo_groups=c.repo_groups_choices,
158 repo_groups=c.repo_groups_choices,
159 landing_revs=c.landing_revs_choices)()
159 landing_revs=c.landing_revs_choices)()
160 form_result = {}
160 form_result = {}
161 try:
161 try:
162 form_result = _form.to_python(dict(request.POST))
162 form_result = _form.to_python(dict(request.POST))
163
163
164 # an approximation that is better than nothing
164 # an approximation that is better than nothing
165 if not RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active:
165 if not RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE).ui_active:
166 form_result['update_after_clone'] = False
166 form_result['update_after_clone'] = False
167
167
168 # create fork is done sometimes async on celery, db transaction
168 # create fork is done sometimes async on celery, db transaction
169 # management is handled there.
169 # management is handled there.
170 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
170 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
171 fork_url = h.link_to(form_result['repo_name_full'],
171 fork_url = h.link_to(form_result['repo_name_full'],
172 h.url('summary_home', repo_name=form_result['repo_name_full']))
172 h.url('summary_home', repo_name=form_result['repo_name_full']))
173
173
174 h.flash(h.literal(_('Forked repository %s as %s') \
174 h.flash(h.literal(_('Forked repository %s as %s') \
175 % (repo_name, fork_url)),
175 % (repo_name, fork_url)),
176 category='success')
176 category='success')
177 except formencode.Invalid, errors:
177 except formencode.Invalid, errors:
178 c.new_repo = errors.value['repo_name']
178 c.new_repo = errors.value['repo_name']
179
179
180 return htmlfill.render(
180 return htmlfill.render(
181 render('forks/fork.html'),
181 render('forks/fork.html'),
182 defaults=errors.value,
182 defaults=errors.value,
183 errors=errors.error_dict or {},
183 errors=errors.error_dict or {},
184 prefix_error=False,
184 prefix_error=False,
185 encoding="UTF-8")
185 encoding="UTF-8")
186 except Exception:
186 except Exception:
187 log.error(traceback.format_exc())
187 log.error(traceback.format_exc())
188 h.flash(_('An error occurred during repository forking %s') %
188 h.flash(_('An error occurred during repository forking %s') %
189 repo_name, category='error')
189 repo_name, category='error')
190
190
191 return redirect(h.url('summary_home', repo_name=repo_name))
191 return redirect(h.url('summary_home', repo_name=repo_name))
@@ -1,379 +1,379 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 sqlalchemy.sql.expression import func
30 from sqlalchemy.sql.expression import func
31
31
32 from webhelpers.paginate import Page
32 from webhelpers.paginate import Page
33 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
33 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
34
34
35 from webob.exc import HTTPBadRequest
35 from webob.exc import HTTPBadRequest
36 from pylons import request, tmpl_context as c, response, url
36 from pylons import request, tmpl_context as c, response, url
37 from pylons.i18n.translation import _
37 from pylons.i18n.translation import _
38
38
39 import rhodecode.lib.helpers as h
39 import rhodecode.lib.helpers as h
40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
40 from rhodecode.lib.auth import LoginRequired, NotAnonymous
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
42 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, AttributeDict
45 from rhodecode.controllers.admin.admin import _journal_filter
45 from rhodecode.controllers.admin.admin import _journal_filter
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47 from rhodecode.lib.compat import json
47 from rhodecode.lib.compat import json
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class JournalController(BaseController):
52 class JournalController(BaseController):
53
53
54 def __before__(self):
54 def __before__(self):
55 super(JournalController, self).__before__()
55 super(JournalController, self).__before__()
56 self.language = 'en-us'
56 self.language = 'en-us'
57 self.ttl = "5"
57 self.ttl = "5"
58 self.feed_nr = 20
58 self.feed_nr = 20
59 c.search_term = request.GET.get('filter')
59 c.search_term = request.GET.get('filter')
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @NotAnonymous()
62 @NotAnonymous()
63 def index(self):
63 def index(self):
64 # Return a rendered template
64 # Return a rendered template
65 p = safe_int(request.params.get('page', 1), 1)
65 p = safe_int(request.GET.get('page', 1), 1)
66 c.user = User.get(self.rhodecode_user.user_id)
66 c.user = User.get(self.rhodecode_user.user_id)
67 c.following = self.sa.query(UserFollowing)\
67 c.following = self.sa.query(UserFollowing)\
68 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
68 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
69 .options(joinedload(UserFollowing.follows_repository))\
69 .options(joinedload(UserFollowing.follows_repository))\
70 .all()
70 .all()
71
71
72 journal = self._get_journal_data(c.following)
72 journal = self._get_journal_data(c.following)
73
73
74 def url_generator(**kw):
74 def url_generator(**kw):
75 return url.current(filter=c.search_term, **kw)
75 return url.current(filter=c.search_term, **kw)
76
76
77 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
77 c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79
79
80 c.journal_data = render('journal/journal_data.html')
80 c.journal_data = render('journal/journal_data.html')
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 return c.journal_data
82 return c.journal_data
83
83
84 repos_list = Session().query(Repository)\
84 repos_list = Session().query(Repository)\
85 .filter(Repository.user_id ==
85 .filter(Repository.user_id ==
86 self.rhodecode_user.user_id)\
86 self.rhodecode_user.user_id)\
87 .order_by(func.lower(Repository.repo_name)).all()
87 .order_by(func.lower(Repository.repo_name)).all()
88
88
89 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
89 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
90 admin=True)
90 admin=True)
91 #json used to render the grid
91 #json used to render the grid
92 c.data = json.dumps(repos_data)
92 c.data = json.dumps(repos_data)
93
93
94 watched_repos_data = []
94 watched_repos_data = []
95
95
96 ## watched repos
96 ## watched repos
97 _render = RepoModel._render_datatable
97 _render = RepoModel._render_datatable
98
98
99 def quick_menu(repo_name):
99 def quick_menu(repo_name):
100 return _render('quick_menu', repo_name)
100 return _render('quick_menu', repo_name)
101
101
102 def repo_lnk(name, rtype, private, fork_of):
102 def repo_lnk(name, rtype, private, fork_of):
103 return _render('repo_name', name, rtype, private, fork_of,
103 return _render('repo_name', name, rtype, private, fork_of,
104 short_name=False, admin=False)
104 short_name=False, admin=False)
105
105
106 def last_rev(repo_name, cs_cache):
106 def last_rev(repo_name, cs_cache):
107 return _render('revision', repo_name, cs_cache.get('revision'),
107 return _render('revision', repo_name, cs_cache.get('revision'),
108 cs_cache.get('raw_id'), cs_cache.get('author'),
108 cs_cache.get('raw_id'), cs_cache.get('author'),
109 cs_cache.get('message'))
109 cs_cache.get('message'))
110
110
111 def desc(desc):
111 def desc(desc):
112 from pylons import tmpl_context as c
112 from pylons import tmpl_context as c
113 if c.visual.stylify_metatags:
113 if c.visual.stylify_metatags:
114 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
114 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
115 else:
115 else:
116 return h.urlify_text(h.truncate(desc, 60))
116 return h.urlify_text(h.truncate(desc, 60))
117
117
118 def repo_actions(repo_name):
118 def repo_actions(repo_name):
119 return _render('repo_actions', repo_name)
119 return _render('repo_actions', repo_name)
120
120
121 def owner_actions(user_id, username):
121 def owner_actions(user_id, username):
122 return _render('user_name', user_id, username)
122 return _render('user_name', user_id, username)
123
123
124 def toogle_follow(repo_id):
124 def toogle_follow(repo_id):
125 return _render('toggle_follow', repo_id)
125 return _render('toggle_follow', repo_id)
126
126
127 for entry in c.following:
127 for entry in c.following:
128 repo = entry.follows_repository
128 repo = entry.follows_repository
129 cs_cache = repo.changeset_cache
129 cs_cache = repo.changeset_cache
130 row = {
130 row = {
131 "menu": quick_menu(repo.repo_name),
131 "menu": quick_menu(repo.repo_name),
132 "raw_name": repo.repo_name.lower(),
132 "raw_name": repo.repo_name.lower(),
133 "name": repo_lnk(repo.repo_name, repo.repo_type,
133 "name": repo_lnk(repo.repo_name, repo.repo_type,
134 repo.private, repo.fork),
134 repo.private, repo.fork),
135 "last_changeset": last_rev(repo.repo_name, cs_cache),
135 "last_changeset": last_rev(repo.repo_name, cs_cache),
136 "raw_tip": cs_cache.get('revision'),
136 "raw_tip": cs_cache.get('revision'),
137 "action": toogle_follow(repo.repo_id)
137 "action": toogle_follow(repo.repo_id)
138 }
138 }
139
139
140 watched_repos_data.append(row)
140 watched_repos_data.append(row)
141
141
142 c.watched_data = json.dumps({
142 c.watched_data = json.dumps({
143 "totalRecords": len(c.following),
143 "totalRecords": len(c.following),
144 "startIndex": 0,
144 "startIndex": 0,
145 "sort": "name",
145 "sort": "name",
146 "dir": "asc",
146 "dir": "asc",
147 "records": watched_repos_data
147 "records": watched_repos_data
148 })
148 })
149 return render('journal/journal.html')
149 return render('journal/journal.html')
150
150
151 @LoginRequired(api_access=True)
151 @LoginRequired(api_access=True)
152 @NotAnonymous()
152 @NotAnonymous()
153 def journal_atom(self):
153 def journal_atom(self):
154 """
154 """
155 Produce an atom-1.0 feed via feedgenerator module
155 Produce an atom-1.0 feed via feedgenerator module
156 """
156 """
157 following = self.sa.query(UserFollowing)\
157 following = self.sa.query(UserFollowing)\
158 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
158 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
159 .options(joinedload(UserFollowing.follows_repository))\
159 .options(joinedload(UserFollowing.follows_repository))\
160 .all()
160 .all()
161 return self._atom_feed(following, public=False)
161 return self._atom_feed(following, public=False)
162
162
163 @LoginRequired(api_access=True)
163 @LoginRequired(api_access=True)
164 @NotAnonymous()
164 @NotAnonymous()
165 def journal_rss(self):
165 def journal_rss(self):
166 """
166 """
167 Produce an rss feed via feedgenerator module
167 Produce an rss feed via feedgenerator module
168 """
168 """
169 following = self.sa.query(UserFollowing)\
169 following = self.sa.query(UserFollowing)\
170 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
170 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
171 .options(joinedload(UserFollowing.follows_repository))\
171 .options(joinedload(UserFollowing.follows_repository))\
172 .all()
172 .all()
173 return self._rss_feed(following, public=False)
173 return self._rss_feed(following, public=False)
174
174
175 def _get_daily_aggregate(self, journal):
175 def _get_daily_aggregate(self, journal):
176 groups = []
176 groups = []
177 for k, g in groupby(journal, lambda x: x.action_as_day):
177 for k, g in groupby(journal, lambda x: x.action_as_day):
178 user_group = []
178 user_group = []
179 #groupby username if it's a present value, else fallback to journal username
179 #groupby username if it's a present value, else fallback to journal username
180 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
180 for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
181 l = list(g2)
181 l = list(g2)
182 user_group.append((l[0].user, l))
182 user_group.append((l[0].user, l))
183
183
184 groups.append((k, user_group,))
184 groups.append((k, user_group,))
185
185
186 return groups
186 return groups
187
187
188 def _get_journal_data(self, following_repos):
188 def _get_journal_data(self, following_repos):
189 repo_ids = [x.follows_repository.repo_id for x in following_repos
189 repo_ids = [x.follows_repository.repo_id for x in following_repos
190 if x.follows_repository is not None]
190 if x.follows_repository is not None]
191 user_ids = [x.follows_user.user_id for x in following_repos
191 user_ids = [x.follows_user.user_id for x in following_repos
192 if x.follows_user is not None]
192 if x.follows_user is not None]
193
193
194 filtering_criterion = None
194 filtering_criterion = None
195
195
196 if repo_ids and user_ids:
196 if repo_ids and user_ids:
197 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
197 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
198 UserLog.user_id.in_(user_ids))
198 UserLog.user_id.in_(user_ids))
199 if repo_ids and not user_ids:
199 if repo_ids and not user_ids:
200 filtering_criterion = UserLog.repository_id.in_(repo_ids)
200 filtering_criterion = UserLog.repository_id.in_(repo_ids)
201 if not repo_ids and user_ids:
201 if not repo_ids and user_ids:
202 filtering_criterion = UserLog.user_id.in_(user_ids)
202 filtering_criterion = UserLog.user_id.in_(user_ids)
203 if filtering_criterion is not None:
203 if filtering_criterion is not None:
204 journal = self.sa.query(UserLog)\
204 journal = self.sa.query(UserLog)\
205 .options(joinedload(UserLog.user))\
205 .options(joinedload(UserLog.user))\
206 .options(joinedload(UserLog.repository))
206 .options(joinedload(UserLog.repository))
207 #filter
207 #filter
208 try:
208 try:
209 journal = _journal_filter(journal, c.search_term)
209 journal = _journal_filter(journal, c.search_term)
210 except Exception:
210 except Exception:
211 # we want this to crash for now
211 # we want this to crash for now
212 raise
212 raise
213 journal = journal.filter(filtering_criterion)\
213 journal = journal.filter(filtering_criterion)\
214 .order_by(UserLog.action_date.desc())
214 .order_by(UserLog.action_date.desc())
215 else:
215 else:
216 journal = []
216 journal = []
217
217
218 return journal
218 return journal
219
219
220 @LoginRequired()
220 @LoginRequired()
221 @NotAnonymous()
221 @NotAnonymous()
222 def toggle_following(self):
222 def toggle_following(self):
223 cur_token = request.POST.get('auth_token')
223 cur_token = request.POST.get('auth_token')
224 token = h.get_token()
224 token = h.get_token()
225 if cur_token == token:
225 if cur_token == token:
226
226
227 user_id = request.POST.get('follows_user_id')
227 user_id = request.POST.get('follows_user_id')
228 if user_id:
228 if user_id:
229 try:
229 try:
230 self.scm_model.toggle_following_user(user_id,
230 self.scm_model.toggle_following_user(user_id,
231 self.rhodecode_user.user_id)
231 self.rhodecode_user.user_id)
232 Session.commit()
232 Session.commit()
233 return 'ok'
233 return 'ok'
234 except Exception:
234 except Exception:
235 raise HTTPBadRequest()
235 raise HTTPBadRequest()
236
236
237 repo_id = request.POST.get('follows_repo_id')
237 repo_id = request.POST.get('follows_repo_id')
238 if repo_id:
238 if repo_id:
239 try:
239 try:
240 self.scm_model.toggle_following_repo(repo_id,
240 self.scm_model.toggle_following_repo(repo_id,
241 self.rhodecode_user.user_id)
241 self.rhodecode_user.user_id)
242 Session.commit()
242 Session.commit()
243 return 'ok'
243 return 'ok'
244 except Exception:
244 except Exception:
245 raise HTTPBadRequest()
245 raise HTTPBadRequest()
246
246
247 log.debug('token mismatch %s vs %s' % (cur_token, token))
247 log.debug('token mismatch %s vs %s' % (cur_token, token))
248 raise HTTPBadRequest()
248 raise HTTPBadRequest()
249
249
250 @LoginRequired()
250 @LoginRequired()
251 def public_journal(self):
251 def public_journal(self):
252 # Return a rendered template
252 # Return a rendered template
253 p = safe_int(request.params.get('page', 1), 1)
253 p = safe_int(request.GET.get('page', 1), 1)
254
254
255 c.following = self.sa.query(UserFollowing)\
255 c.following = self.sa.query(UserFollowing)\
256 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
256 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
257 .options(joinedload(UserFollowing.follows_repository))\
257 .options(joinedload(UserFollowing.follows_repository))\
258 .all()
258 .all()
259
259
260 journal = self._get_journal_data(c.following)
260 journal = self._get_journal_data(c.following)
261
261
262 c.journal_pager = Page(journal, page=p, items_per_page=20)
262 c.journal_pager = Page(journal, page=p, items_per_page=20)
263
263
264 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
264 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
265
265
266 c.journal_data = render('journal/journal_data.html')
266 c.journal_data = render('journal/journal_data.html')
267 if request.environ.get('HTTP_X_PARTIAL_XHR'):
267 if request.environ.get('HTTP_X_PARTIAL_XHR'):
268 return c.journal_data
268 return c.journal_data
269 return render('journal/public_journal.html')
269 return render('journal/public_journal.html')
270
270
271 def _atom_feed(self, repos, public=True):
271 def _atom_feed(self, repos, public=True):
272 journal = self._get_journal_data(repos)
272 journal = self._get_journal_data(repos)
273 if public:
273 if public:
274 _link = url('public_journal_atom', qualified=True)
274 _link = url('public_journal_atom', qualified=True)
275 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
275 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
276 'atom feed')
276 'atom feed')
277 else:
277 else:
278 _link = url('journal_atom', qualified=True)
278 _link = url('journal_atom', qualified=True)
279 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
279 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
280
280
281 feed = Atom1Feed(title=_desc,
281 feed = Atom1Feed(title=_desc,
282 link=_link,
282 link=_link,
283 description=_desc,
283 description=_desc,
284 language=self.language,
284 language=self.language,
285 ttl=self.ttl)
285 ttl=self.ttl)
286
286
287 for entry in journal[:self.feed_nr]:
287 for entry in journal[:self.feed_nr]:
288 user = entry.user
288 user = entry.user
289 if user is None:
289 if user is None:
290 #fix deleted users
290 #fix deleted users
291 user = AttributeDict({'short_contact': entry.username,
291 user = AttributeDict({'short_contact': entry.username,
292 'email': '',
292 'email': '',
293 'full_contact': ''})
293 'full_contact': ''})
294 action, action_extra, ico = h.action_parser(entry, feed=True)
294 action, action_extra, ico = h.action_parser(entry, feed=True)
295 title = "%s - %s %s" % (user.short_contact, action(),
295 title = "%s - %s %s" % (user.short_contact, action(),
296 entry.repository.repo_name)
296 entry.repository.repo_name)
297 desc = action_extra()
297 desc = action_extra()
298 _url = None
298 _url = None
299 if entry.repository is not None:
299 if entry.repository is not None:
300 _url = url('changelog_home',
300 _url = url('changelog_home',
301 repo_name=entry.repository.repo_name,
301 repo_name=entry.repository.repo_name,
302 qualified=True)
302 qualified=True)
303
303
304 feed.add_item(title=title,
304 feed.add_item(title=title,
305 pubdate=entry.action_date,
305 pubdate=entry.action_date,
306 link=_url or url('', qualified=True),
306 link=_url or url('', qualified=True),
307 author_email=user.email,
307 author_email=user.email,
308 author_name=user.full_contact,
308 author_name=user.full_contact,
309 description=desc)
309 description=desc)
310
310
311 response.content_type = feed.mime_type
311 response.content_type = feed.mime_type
312 return feed.writeString('utf-8')
312 return feed.writeString('utf-8')
313
313
314 def _rss_feed(self, repos, public=True):
314 def _rss_feed(self, repos, public=True):
315 journal = self._get_journal_data(repos)
315 journal = self._get_journal_data(repos)
316 if public:
316 if public:
317 _link = url('public_journal_atom', qualified=True)
317 _link = url('public_journal_atom', qualified=True)
318 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
318 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
319 'rss feed')
319 'rss feed')
320 else:
320 else:
321 _link = url('journal_atom', qualified=True)
321 _link = url('journal_atom', qualified=True)
322 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
322 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
323
323
324 feed = Rss201rev2Feed(title=_desc,
324 feed = Rss201rev2Feed(title=_desc,
325 link=_link,
325 link=_link,
326 description=_desc,
326 description=_desc,
327 language=self.language,
327 language=self.language,
328 ttl=self.ttl)
328 ttl=self.ttl)
329
329
330 for entry in journal[:self.feed_nr]:
330 for entry in journal[:self.feed_nr]:
331 user = entry.user
331 user = entry.user
332 if user is None:
332 if user is None:
333 #fix deleted users
333 #fix deleted users
334 user = AttributeDict({'short_contact': entry.username,
334 user = AttributeDict({'short_contact': entry.username,
335 'email': '',
335 'email': '',
336 'full_contact': ''})
336 'full_contact': ''})
337 action, action_extra, ico = h.action_parser(entry, feed=True)
337 action, action_extra, ico = h.action_parser(entry, feed=True)
338 title = "%s - %s %s" % (user.short_contact, action(),
338 title = "%s - %s %s" % (user.short_contact, action(),
339 entry.repository.repo_name)
339 entry.repository.repo_name)
340 desc = action_extra()
340 desc = action_extra()
341 _url = None
341 _url = None
342 if entry.repository is not None:
342 if entry.repository is not None:
343 _url = url('changelog_home',
343 _url = url('changelog_home',
344 repo_name=entry.repository.repo_name,
344 repo_name=entry.repository.repo_name,
345 qualified=True)
345 qualified=True)
346
346
347 feed.add_item(title=title,
347 feed.add_item(title=title,
348 pubdate=entry.action_date,
348 pubdate=entry.action_date,
349 link=_url or url('', qualified=True),
349 link=_url or url('', qualified=True),
350 author_email=user.email,
350 author_email=user.email,
351 author_name=user.full_contact,
351 author_name=user.full_contact,
352 description=desc)
352 description=desc)
353
353
354 response.content_type = feed.mime_type
354 response.content_type = feed.mime_type
355 return feed.writeString('utf-8')
355 return feed.writeString('utf-8')
356
356
357 @LoginRequired(api_access=True)
357 @LoginRequired(api_access=True)
358 def public_journal_atom(self):
358 def public_journal_atom(self):
359 """
359 """
360 Produce an atom-1.0 feed via feedgenerator module
360 Produce an atom-1.0 feed via feedgenerator module
361 """
361 """
362 c.following = self.sa.query(UserFollowing)\
362 c.following = self.sa.query(UserFollowing)\
363 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
363 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
364 .options(joinedload(UserFollowing.follows_repository))\
364 .options(joinedload(UserFollowing.follows_repository))\
365 .all()
365 .all()
366
366
367 return self._atom_feed(c.following)
367 return self._atom_feed(c.following)
368
368
369 @LoginRequired(api_access=True)
369 @LoginRequired(api_access=True)
370 def public_journal_rss(self):
370 def public_journal_rss(self):
371 """
371 """
372 Produce an rss2 feed via feedgenerator module
372 Produce an rss2 feed via feedgenerator module
373 """
373 """
374 c.following = self.sa.query(UserFollowing)\
374 c.following = self.sa.query(UserFollowing)\
375 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
375 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
376 .options(joinedload(UserFollowing.follows_repository))\
376 .options(joinedload(UserFollowing.follows_repository))\
377 .all()
377 .all()
378
378
379 return self._rss_feed(c.following)
379 return self._rss_feed(c.following)
@@ -1,521 +1,521 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
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 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 @LoginRequired()
65 @LoginRequired()
66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
67 'repository.admin')
67 'repository.admin')
68 def __before__(self):
68 def __before__(self):
69 super(PullrequestsController, self).__before__()
69 super(PullrequestsController, self).__before__()
70 repo_model = RepoModel()
70 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
73
73
74 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
74 def _get_repo_refs(self, repo, rev=None, branch_rev=None):
75 """return a structure with repo's interesting changesets, suitable for
75 """return a structure with repo's interesting changesets, suitable for
76 the selectors in pullrequest.html"""
76 the selectors in pullrequest.html"""
77 # list named branches that has been merged to this named branch - it should probably merge back
77 # list named branches that has been merged to this named branch - it should probably merge back
78 peers = []
78 peers = []
79
79
80 if rev:
80 if rev:
81 rev = safe_str(rev)
81 rev = safe_str(rev)
82
82
83 if branch_rev:
83 if branch_rev:
84 branch_rev = safe_str(branch_rev)
84 branch_rev = safe_str(branch_rev)
85 # not restricting to merge() would also get branch point and be better
85 # not restricting to merge() would also get branch point and be better
86 # (especially because it would get the branch point) ... but is currently too expensive
86 # (especially because it would get the branch point) ... but is currently too expensive
87 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
87 revs = ["sort(parents(branch(id('%s')) and merge()) - branch(id('%s')))" %
88 (branch_rev, branch_rev)]
88 (branch_rev, branch_rev)]
89 otherbranches = {}
89 otherbranches = {}
90 for i in scmutil.revrange(repo._repo, revs):
90 for i in scmutil.revrange(repo._repo, revs):
91 cs = repo.get_changeset(i)
91 cs = repo.get_changeset(i)
92 otherbranches[cs.branch] = cs.raw_id
92 otherbranches[cs.branch] = cs.raw_id
93 for branch, node in otherbranches.iteritems():
93 for branch, node in otherbranches.iteritems():
94 selected = 'branch:%s:%s' % (branch, node)
94 selected = 'branch:%s:%s' % (branch, node)
95 peers.append((selected, branch))
95 peers.append((selected, branch))
96
96
97 selected = None
97 selected = None
98 branches = []
98 branches = []
99 for branch, branchrev in repo.branches.iteritems():
99 for branch, branchrev in repo.branches.iteritems():
100 n = 'branch:%s:%s' % (branch, branchrev)
100 n = 'branch:%s:%s' % (branch, branchrev)
101 branches.append((n, branch))
101 branches.append((n, branch))
102 if rev == branchrev:
102 if rev == branchrev:
103 selected = n
103 selected = n
104 bookmarks = []
104 bookmarks = []
105 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
105 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
106 n = 'book:%s:%s' % (bookmark, bookmarkrev)
106 n = 'book:%s:%s' % (bookmark, bookmarkrev)
107 bookmarks.append((n, bookmark))
107 bookmarks.append((n, bookmark))
108 if rev == bookmarkrev:
108 if rev == bookmarkrev:
109 selected = n
109 selected = n
110 tags = []
110 tags = []
111 for tag, tagrev in repo.tags.iteritems():
111 for tag, tagrev in repo.tags.iteritems():
112 n = 'tag:%s:%s' % (tag, tagrev)
112 n = 'tag:%s:%s' % (tag, tagrev)
113 tags.append((n, tag))
113 tags.append((n, tag))
114 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
114 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
115 selected = n
115 selected = n
116
116
117 # prio 1: rev was selected as existing entry above
117 # prio 1: rev was selected as existing entry above
118
118
119 # prio 2: create special entry for rev; rev _must_ be used
119 # prio 2: create special entry for rev; rev _must_ be used
120 specials = []
120 specials = []
121 if rev and selected is None:
121 if rev and selected is None:
122 selected = 'rev:%s:%s' % (rev, rev)
122 selected = 'rev:%s:%s' % (rev, rev)
123 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
123 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
124
124
125 # prio 3: most recent peer branch
125 # prio 3: most recent peer branch
126 if peers and not selected:
126 if peers and not selected:
127 selected = peers[0][0][0]
127 selected = peers[0][0][0]
128
128
129 # prio 4: tip revision
129 # prio 4: tip revision
130 if not selected:
130 if not selected:
131 selected = 'tag:tip:%s' % repo.tags['tip']
131 selected = 'tag:tip:%s' % repo.tags['tip']
132
132
133 groups = [(specials, _("Special")),
133 groups = [(specials, _("Special")),
134 (peers, _("Peer branches")),
134 (peers, _("Peer branches")),
135 (bookmarks, _("Bookmarks")),
135 (bookmarks, _("Bookmarks")),
136 (branches, _("Branches")),
136 (branches, _("Branches")),
137 (tags, _("Tags")),
137 (tags, _("Tags")),
138 ]
138 ]
139 return [g for g in groups if g[0]], selected
139 return [g for g in groups if g[0]], selected
140
140
141 def _get_is_allowed_change_status(self, pull_request):
141 def _get_is_allowed_change_status(self, pull_request):
142 owner = self.rhodecode_user.user_id == pull_request.user_id
142 owner = self.rhodecode_user.user_id == pull_request.user_id
143 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
143 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
144 pull_request.reviewers]
144 pull_request.reviewers]
145 return (self.rhodecode_user.admin or owner or reviewer)
145 return (self.rhodecode_user.admin or owner or reviewer)
146
146
147 def show_all(self, repo_name):
147 def show_all(self, repo_name):
148 c.pull_requests = PullRequestModel().get_all(repo_name)
148 c.pull_requests = PullRequestModel().get_all(repo_name)
149 c.repo_name = repo_name
149 c.repo_name = repo_name
150 p = safe_int(request.params.get('page', 1), 1)
150 p = safe_int(request.GET.get('page', 1), 1)
151
151
152 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
152 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
153
153
154 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
154 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
155
155
156 if request.environ.get('HTTP_X_PARTIAL_XHR'):
156 if request.environ.get('HTTP_X_PARTIAL_XHR'):
157 return c.pullrequest_data
157 return c.pullrequest_data
158
158
159 return render('/pullrequests/pullrequest_show_all.html')
159 return render('/pullrequests/pullrequest_show_all.html')
160
160
161 @NotAnonymous()
161 @NotAnonymous()
162 def index(self):
162 def index(self):
163 org_repo = c.rhodecode_db_repo
163 org_repo = c.rhodecode_db_repo
164
164
165 if org_repo.scm_instance.alias != 'hg':
165 if org_repo.scm_instance.alias != 'hg':
166 log.error('Review not available for GIT REPOS')
166 log.error('Review not available for GIT REPOS')
167 raise HTTPNotFound
167 raise HTTPNotFound
168
168
169 try:
169 try:
170 org_repo.scm_instance.get_changeset()
170 org_repo.scm_instance.get_changeset()
171 except EmptyRepositoryError, e:
171 except EmptyRepositoryError, e:
172 h.flash(h.literal(_('There are no changesets yet')),
172 h.flash(h.literal(_('There are no changesets yet')),
173 category='warning')
173 category='warning')
174 redirect(url('summary_home', repo_name=org_repo.repo_name))
174 redirect(url('summary_home', repo_name=org_repo.repo_name))
175
175
176 org_rev = request.GET.get('rev_end')
176 org_rev = request.GET.get('rev_end')
177 # rev_start is not directly useful - its parent could however be used
177 # rev_start is not directly useful - its parent could however be used
178 # as default for other and thus give a simple compare view
178 # as default for other and thus give a simple compare view
179 #other_rev = request.POST.get('rev_start')
179 #other_rev = request.POST.get('rev_start')
180
180
181 c.org_repos = []
181 c.org_repos = []
182 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
182 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
183 c.default_org_repo = org_repo.repo_name
183 c.default_org_repo = org_repo.repo_name
184 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
184 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, org_rev)
185
185
186 c.other_repos = []
186 c.other_repos = []
187 other_repos_info = {}
187 other_repos_info = {}
188
188
189 def add_other_repo(repo, branch_rev=None):
189 def add_other_repo(repo, branch_rev=None):
190 if repo.repo_name in other_repos_info: # shouldn't happen
190 if repo.repo_name in other_repos_info: # shouldn't happen
191 return
191 return
192 c.other_repos.append((repo.repo_name, repo.repo_name))
192 c.other_repos.append((repo.repo_name, repo.repo_name))
193 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
193 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
194 other_repos_info[repo.repo_name] = {
194 other_repos_info[repo.repo_name] = {
195 'user': dict(user_id=repo.user.user_id,
195 'user': dict(user_id=repo.user.user_id,
196 username=repo.user.username,
196 username=repo.user.username,
197 firstname=repo.user.firstname,
197 firstname=repo.user.firstname,
198 lastname=repo.user.lastname,
198 lastname=repo.user.lastname,
199 gravatar_link=h.gravatar_url(repo.user.email, 14)),
199 gravatar_link=h.gravatar_url(repo.user.email, 14)),
200 'description': repo.description.split('\n', 1)[0],
200 'description': repo.description.split('\n', 1)[0],
201 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
201 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
202 }
202 }
203
203
204 # add org repo to other so we can open pull request against peer branches on itself
204 # add org repo to other so we can open pull request against peer branches on itself
205 add_other_repo(org_repo, branch_rev=org_rev)
205 add_other_repo(org_repo, branch_rev=org_rev)
206 c.default_other_repo = org_repo.repo_name
206 c.default_other_repo = org_repo.repo_name
207
207
208 # gather forks and add to this list ... even though it is rare to
208 # gather forks and add to this list ... even though it is rare to
209 # request forks to pull from their parent
209 # request forks to pull from their parent
210 for fork in org_repo.forks:
210 for fork in org_repo.forks:
211 add_other_repo(fork)
211 add_other_repo(fork)
212
212
213 # add parents of this fork also, but only if it's not empty
213 # add parents of this fork also, but only if it's not empty
214 if org_repo.parent and org_repo.parent.scm_instance.revisions:
214 if org_repo.parent and org_repo.parent.scm_instance.revisions:
215 add_other_repo(org_repo.parent)
215 add_other_repo(org_repo.parent)
216 c.default_other_repo = org_repo.parent.repo_name
216 c.default_other_repo = org_repo.parent.repo_name
217
217
218 c.default_other_repo_info = other_repos_info[c.default_other_repo]
218 c.default_other_repo_info = other_repos_info[c.default_other_repo]
219 c.other_repos_info = json.dumps(other_repos_info)
219 c.other_repos_info = json.dumps(other_repos_info)
220
220
221 return render('/pullrequests/pullrequest.html')
221 return render('/pullrequests/pullrequest.html')
222
222
223 @NotAnonymous()
223 @NotAnonymous()
224 def create(self, repo_name):
224 def create(self, repo_name):
225 repo = RepoModel()._get_repo(repo_name)
225 repo = RepoModel()._get_repo(repo_name)
226 try:
226 try:
227 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
227 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
228 except formencode.Invalid, errors:
228 except formencode.Invalid, errors:
229 log.error(traceback.format_exc())
229 log.error(traceback.format_exc())
230 if errors.error_dict.get('revisions'):
230 if errors.error_dict.get('revisions'):
231 msg = 'Revisions: %s' % errors.error_dict['revisions']
231 msg = 'Revisions: %s' % errors.error_dict['revisions']
232 elif errors.error_dict.get('pullrequest_title'):
232 elif errors.error_dict.get('pullrequest_title'):
233 msg = _('Pull request requires a title with min. 3 chars')
233 msg = _('Pull request requires a title with min. 3 chars')
234 else:
234 else:
235 msg = _('Error creating pull request')
235 msg = _('Error creating pull request')
236
236
237 h.flash(msg, 'error')
237 h.flash(msg, 'error')
238 return redirect(url('pullrequest_home', repo_name=repo_name))
238 return redirect(url('pullrequest_home', repo_name=repo_name))
239
239
240 org_repo = _form['org_repo']
240 org_repo = _form['org_repo']
241 org_ref = 'rev:merge:%s' % _form['merge_rev']
241 org_ref = 'rev:merge:%s' % _form['merge_rev']
242 other_repo = _form['other_repo']
242 other_repo = _form['other_repo']
243 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
243 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
244 revisions = reversed(_form['revisions'])
244 revisions = reversed(_form['revisions'])
245 reviewers = _form['review_members']
245 reviewers = _form['review_members']
246
246
247 title = _form['pullrequest_title']
247 title = _form['pullrequest_title']
248 description = _form['pullrequest_desc']
248 description = _form['pullrequest_desc']
249
249
250 try:
250 try:
251 pull_request = PullRequestModel().create(
251 pull_request = PullRequestModel().create(
252 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
252 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
253 other_ref, revisions, reviewers, title, description
253 other_ref, revisions, reviewers, title, description
254 )
254 )
255 Session().commit()
255 Session().commit()
256 h.flash(_('Successfully opened new pull request'),
256 h.flash(_('Successfully opened new pull request'),
257 category='success')
257 category='success')
258 except Exception:
258 except Exception:
259 h.flash(_('Error occurred during sending pull request'),
259 h.flash(_('Error occurred during sending pull request'),
260 category='error')
260 category='error')
261 log.error(traceback.format_exc())
261 log.error(traceback.format_exc())
262 return redirect(url('pullrequest_home', repo_name=repo_name))
262 return redirect(url('pullrequest_home', repo_name=repo_name))
263
263
264 return redirect(url('pullrequest_show', repo_name=other_repo,
264 return redirect(url('pullrequest_show', repo_name=other_repo,
265 pull_request_id=pull_request.pull_request_id))
265 pull_request_id=pull_request.pull_request_id))
266
266
267 @NotAnonymous()
267 @NotAnonymous()
268 @jsonify
268 @jsonify
269 def update(self, repo_name, pull_request_id):
269 def update(self, repo_name, pull_request_id):
270 pull_request = PullRequest.get_or_404(pull_request_id)
270 pull_request = PullRequest.get_or_404(pull_request_id)
271 if pull_request.is_closed():
271 if pull_request.is_closed():
272 raise HTTPForbidden()
272 raise HTTPForbidden()
273 #only owner or admin can update it
273 #only owner or admin can update it
274 owner = pull_request.author.user_id == c.rhodecode_user.user_id
274 owner = pull_request.author.user_id == c.rhodecode_user.user_id
275 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
275 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
276 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
276 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
277 request.POST.get('reviewers_ids', '').split(',')))
277 request.POST.get('reviewers_ids', '').split(',')))
278
278
279 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
279 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
280 Session().commit()
280 Session().commit()
281 return True
281 return True
282 raise HTTPForbidden()
282 raise HTTPForbidden()
283
283
284 @NotAnonymous()
284 @NotAnonymous()
285 @jsonify
285 @jsonify
286 def delete(self, repo_name, pull_request_id):
286 def delete(self, repo_name, pull_request_id):
287 pull_request = PullRequest.get_or_404(pull_request_id)
287 pull_request = PullRequest.get_or_404(pull_request_id)
288 #only owner can delete it !
288 #only owner can delete it !
289 if pull_request.author.user_id == c.rhodecode_user.user_id:
289 if pull_request.author.user_id == c.rhodecode_user.user_id:
290 PullRequestModel().delete(pull_request)
290 PullRequestModel().delete(pull_request)
291 Session().commit()
291 Session().commit()
292 h.flash(_('Successfully deleted pull request'),
292 h.flash(_('Successfully deleted pull request'),
293 category='success')
293 category='success')
294 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
294 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
295 raise HTTPForbidden()
295 raise HTTPForbidden()
296
296
297 def _load_compare_data(self, pull_request, enable_comments=True):
297 def _load_compare_data(self, pull_request, enable_comments=True):
298 """
298 """
299 Load context data needed for generating compare diff
299 Load context data needed for generating compare diff
300
300
301 :param pull_request:
301 :param pull_request:
302 :type pull_request:
302 :type pull_request:
303 """
303 """
304 org_repo = pull_request.org_repo
304 org_repo = pull_request.org_repo
305 (org_ref_type,
305 (org_ref_type,
306 org_ref_name,
306 org_ref_name,
307 org_ref_rev) = pull_request.org_ref.split(':')
307 org_ref_rev) = pull_request.org_ref.split(':')
308
308
309 other_repo = org_repo
309 other_repo = org_repo
310 (other_ref_type,
310 (other_ref_type,
311 other_ref_name,
311 other_ref_name,
312 other_ref_rev) = pull_request.other_ref.split(':')
312 other_ref_rev) = pull_request.other_ref.split(':')
313
313
314 # despite opening revisions for bookmarks/branches/tags, we always
314 # despite opening revisions for bookmarks/branches/tags, we always
315 # convert this to rev to prevent changes after bookmark or branch change
315 # convert this to rev to prevent changes after bookmark or branch change
316 org_ref = ('rev', org_ref_rev)
316 org_ref = ('rev', org_ref_rev)
317 other_ref = ('rev', other_ref_rev)
317 other_ref = ('rev', other_ref_rev)
318
318
319 c.org_repo = org_repo
319 c.org_repo = org_repo
320 c.other_repo = other_repo
320 c.other_repo = other_repo
321
321
322 c.fulldiff = fulldiff = request.GET.get('fulldiff')
322 c.fulldiff = fulldiff = request.GET.get('fulldiff')
323
323
324 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
324 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
325
325
326 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
326 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
327
327
328 c.org_ref = org_ref[1]
328 c.org_ref = org_ref[1]
329 c.org_ref_type = org_ref[0]
329 c.org_ref_type = org_ref[0]
330 c.other_ref = other_ref[1]
330 c.other_ref = other_ref[1]
331 c.other_ref_type = other_ref[0]
331 c.other_ref_type = other_ref[0]
332
332
333 diff_limit = self.cut_off_limit if not fulldiff else None
333 diff_limit = self.cut_off_limit if not fulldiff else None
334
334
335 # we swap org/other ref since we run a simple diff on one repo
335 # we swap org/other ref since we run a simple diff on one repo
336 log.debug('running diff between %s@%s and %s@%s'
336 log.debug('running diff between %s@%s and %s@%s'
337 % (org_repo.scm_instance.path, org_ref,
337 % (org_repo.scm_instance.path, org_ref,
338 other_repo.scm_instance.path, other_ref))
338 other_repo.scm_instance.path, other_ref))
339 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
339 _diff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
340
340
341 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
341 diff_processor = diffs.DiffProcessor(_diff or '', format='gitdiff',
342 diff_limit=diff_limit)
342 diff_limit=diff_limit)
343 _parsed = diff_processor.prepare()
343 _parsed = diff_processor.prepare()
344
344
345 c.limited_diff = False
345 c.limited_diff = False
346 if isinstance(_parsed, LimitedDiffContainer):
346 if isinstance(_parsed, LimitedDiffContainer):
347 c.limited_diff = True
347 c.limited_diff = True
348
348
349 c.files = []
349 c.files = []
350 c.changes = {}
350 c.changes = {}
351 c.lines_added = 0
351 c.lines_added = 0
352 c.lines_deleted = 0
352 c.lines_deleted = 0
353 for f in _parsed:
353 for f in _parsed:
354 st = f['stats']
354 st = f['stats']
355 if st[0] != 'b':
355 if st[0] != 'b':
356 c.lines_added += st[0]
356 c.lines_added += st[0]
357 c.lines_deleted += st[1]
357 c.lines_deleted += st[1]
358 fid = h.FID('', f['filename'])
358 fid = h.FID('', f['filename'])
359 c.files.append([fid, f['operation'], f['filename'], f['stats']])
359 c.files.append([fid, f['operation'], f['filename'], f['stats']])
360 diff = diff_processor.as_html(enable_comments=enable_comments,
360 diff = diff_processor.as_html(enable_comments=enable_comments,
361 parsed_lines=[f])
361 parsed_lines=[f])
362 c.changes[fid] = [f['operation'], f['filename'], diff]
362 c.changes[fid] = [f['operation'], f['filename'], diff]
363
363
364 def show(self, repo_name, pull_request_id):
364 def show(self, repo_name, pull_request_id):
365 repo_model = RepoModel()
365 repo_model = RepoModel()
366 c.users_array = repo_model.get_users_js()
366 c.users_array = repo_model.get_users_js()
367 c.users_groups_array = repo_model.get_users_groups_js()
367 c.users_groups_array = repo_model.get_users_groups_js()
368 c.pull_request = PullRequest.get_or_404(pull_request_id)
368 c.pull_request = PullRequest.get_or_404(pull_request_id)
369 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
369 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
370 cc_model = ChangesetCommentsModel()
370 cc_model = ChangesetCommentsModel()
371 cs_model = ChangesetStatusModel()
371 cs_model = ChangesetStatusModel()
372 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
372 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
373 pull_request=c.pull_request,
373 pull_request=c.pull_request,
374 with_revisions=True)
374 with_revisions=True)
375
375
376 cs_statuses = defaultdict(list)
376 cs_statuses = defaultdict(list)
377 for st in _cs_statuses:
377 for st in _cs_statuses:
378 cs_statuses[st.author.username] += [st]
378 cs_statuses[st.author.username] += [st]
379
379
380 c.pull_request_reviewers = []
380 c.pull_request_reviewers = []
381 c.pull_request_pending_reviewers = []
381 c.pull_request_pending_reviewers = []
382 for o in c.pull_request.reviewers:
382 for o in c.pull_request.reviewers:
383 st = cs_statuses.get(o.user.username, None)
383 st = cs_statuses.get(o.user.username, None)
384 if st:
384 if st:
385 sorter = lambda k: k.version
385 sorter = lambda k: k.version
386 st = [(x, list(y)[0])
386 st = [(x, list(y)[0])
387 for x, y in (groupby(sorted(st, key=sorter), sorter))]
387 for x, y in (groupby(sorted(st, key=sorter), sorter))]
388 else:
388 else:
389 c.pull_request_pending_reviewers.append(o.user)
389 c.pull_request_pending_reviewers.append(o.user)
390 c.pull_request_reviewers.append([o.user, st])
390 c.pull_request_reviewers.append([o.user, st])
391
391
392 # pull_requests repo_name we opened it against
392 # pull_requests repo_name we opened it against
393 # ie. other_repo must match
393 # ie. other_repo must match
394 if repo_name != c.pull_request.other_repo.repo_name:
394 if repo_name != c.pull_request.other_repo.repo_name:
395 raise HTTPNotFound
395 raise HTTPNotFound
396
396
397 # load compare data into template context
397 # load compare data into template context
398 enable_comments = not c.pull_request.is_closed()
398 enable_comments = not c.pull_request.is_closed()
399 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
399 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
400
400
401 # inline comments
401 # inline comments
402 c.inline_cnt = 0
402 c.inline_cnt = 0
403 c.inline_comments = cc_model.get_inline_comments(
403 c.inline_comments = cc_model.get_inline_comments(
404 c.rhodecode_db_repo.repo_id,
404 c.rhodecode_db_repo.repo_id,
405 pull_request=pull_request_id)
405 pull_request=pull_request_id)
406 # count inline comments
406 # count inline comments
407 for __, lines in c.inline_comments:
407 for __, lines in c.inline_comments:
408 for comments in lines.values():
408 for comments in lines.values():
409 c.inline_cnt += len(comments)
409 c.inline_cnt += len(comments)
410 # comments
410 # comments
411 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
411 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
412 pull_request=pull_request_id)
412 pull_request=pull_request_id)
413
413
414 try:
414 try:
415 cur_status = c.statuses[c.pull_request.revisions[0]][0]
415 cur_status = c.statuses[c.pull_request.revisions[0]][0]
416 except Exception:
416 except Exception:
417 log.error(traceback.format_exc())
417 log.error(traceback.format_exc())
418 cur_status = 'undefined'
418 cur_status = 'undefined'
419 if c.pull_request.is_closed() and 0:
419 if c.pull_request.is_closed() and 0:
420 c.current_changeset_status = cur_status
420 c.current_changeset_status = cur_status
421 else:
421 else:
422 # changeset(pull-request) status calulation based on reviewers
422 # changeset(pull-request) status calulation based on reviewers
423 c.current_changeset_status = cs_model.calculate_status(
423 c.current_changeset_status = cs_model.calculate_status(
424 c.pull_request_reviewers,
424 c.pull_request_reviewers,
425 )
425 )
426 c.changeset_statuses = ChangesetStatus.STATUSES
426 c.changeset_statuses = ChangesetStatus.STATUSES
427
427
428 c.as_form = False
428 c.as_form = False
429 c.ancestor = None # there is one - but right here we don't know which
429 c.ancestor = None # there is one - but right here we don't know which
430 return render('/pullrequests/pullrequest_show.html')
430 return render('/pullrequests/pullrequest_show.html')
431
431
432 @NotAnonymous()
432 @NotAnonymous()
433 @jsonify
433 @jsonify
434 def comment(self, repo_name, pull_request_id):
434 def comment(self, repo_name, pull_request_id):
435 pull_request = PullRequest.get_or_404(pull_request_id)
435 pull_request = PullRequest.get_or_404(pull_request_id)
436 if pull_request.is_closed():
436 if pull_request.is_closed():
437 raise HTTPForbidden()
437 raise HTTPForbidden()
438
438
439 status = request.POST.get('changeset_status')
439 status = request.POST.get('changeset_status')
440 change_status = request.POST.get('change_changeset_status')
440 change_status = request.POST.get('change_changeset_status')
441 text = request.POST.get('text')
441 text = request.POST.get('text')
442 close_pr = request.POST.get('save_close')
442 close_pr = request.POST.get('save_close')
443
443
444 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
444 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
445 if status and change_status and allowed_to_change_status:
445 if status and change_status and allowed_to_change_status:
446 _def = (_('Status change -> %s')
446 _def = (_('Status change -> %s')
447 % ChangesetStatus.get_status_lbl(status))
447 % ChangesetStatus.get_status_lbl(status))
448 if close_pr:
448 if close_pr:
449 _def = _('Closing with') + ' ' + _def
449 _def = _('Closing with') + ' ' + _def
450 text = text or _def
450 text = text or _def
451 comm = ChangesetCommentsModel().create(
451 comm = ChangesetCommentsModel().create(
452 text=text,
452 text=text,
453 repo=c.rhodecode_db_repo.repo_id,
453 repo=c.rhodecode_db_repo.repo_id,
454 user=c.rhodecode_user.user_id,
454 user=c.rhodecode_user.user_id,
455 pull_request=pull_request_id,
455 pull_request=pull_request_id,
456 f_path=request.POST.get('f_path'),
456 f_path=request.POST.get('f_path'),
457 line_no=request.POST.get('line'),
457 line_no=request.POST.get('line'),
458 status_change=(ChangesetStatus.get_status_lbl(status)
458 status_change=(ChangesetStatus.get_status_lbl(status)
459 if status and change_status
459 if status and change_status
460 and allowed_to_change_status else None),
460 and allowed_to_change_status else None),
461 closing_pr=close_pr
461 closing_pr=close_pr
462 )
462 )
463
463
464 action_logger(self.rhodecode_user,
464 action_logger(self.rhodecode_user,
465 'user_commented_pull_request:%s' % pull_request_id,
465 'user_commented_pull_request:%s' % pull_request_id,
466 c.rhodecode_db_repo, self.ip_addr, self.sa)
466 c.rhodecode_db_repo, self.ip_addr, self.sa)
467
467
468 if allowed_to_change_status:
468 if allowed_to_change_status:
469 # get status if set !
469 # get status if set !
470 if status and change_status:
470 if status and change_status:
471 ChangesetStatusModel().set_status(
471 ChangesetStatusModel().set_status(
472 c.rhodecode_db_repo.repo_id,
472 c.rhodecode_db_repo.repo_id,
473 status,
473 status,
474 c.rhodecode_user.user_id,
474 c.rhodecode_user.user_id,
475 comm,
475 comm,
476 pull_request=pull_request_id
476 pull_request=pull_request_id
477 )
477 )
478
478
479 if close_pr:
479 if close_pr:
480 if status in ['rejected', 'approved']:
480 if status in ['rejected', 'approved']:
481 PullRequestModel().close_pull_request(pull_request_id)
481 PullRequestModel().close_pull_request(pull_request_id)
482 action_logger(self.rhodecode_user,
482 action_logger(self.rhodecode_user,
483 'user_closed_pull_request:%s' % pull_request_id,
483 'user_closed_pull_request:%s' % pull_request_id,
484 c.rhodecode_db_repo, self.ip_addr, self.sa)
484 c.rhodecode_db_repo, self.ip_addr, self.sa)
485 else:
485 else:
486 h.flash(_('Closing pull request on other statuses than '
486 h.flash(_('Closing pull request on other statuses than '
487 'rejected or approved forbidden'),
487 'rejected or approved forbidden'),
488 category='warning')
488 category='warning')
489
489
490 Session().commit()
490 Session().commit()
491
491
492 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
492 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
493 return redirect(h.url('pullrequest_show', repo_name=repo_name,
493 return redirect(h.url('pullrequest_show', repo_name=repo_name,
494 pull_request_id=pull_request_id))
494 pull_request_id=pull_request_id))
495
495
496 data = {
496 data = {
497 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
497 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
498 }
498 }
499 if comm:
499 if comm:
500 c.co = comm
500 c.co = comm
501 data.update(comm.get_dict())
501 data.update(comm.get_dict())
502 data.update({'rendered_text':
502 data.update({'rendered_text':
503 render('changeset/changeset_comment_block.html')})
503 render('changeset/changeset_comment_block.html')})
504
504
505 return data
505 return data
506
506
507 @NotAnonymous()
507 @NotAnonymous()
508 @jsonify
508 @jsonify
509 def delete_comment(self, repo_name, comment_id):
509 def delete_comment(self, repo_name, comment_id):
510 co = ChangesetComment.get(comment_id)
510 co = ChangesetComment.get(comment_id)
511 if co.pull_request.is_closed():
511 if co.pull_request.is_closed():
512 #don't allow deleting comments on closed pull request
512 #don't allow deleting comments on closed pull request
513 raise HTTPForbidden()
513 raise HTTPForbidden()
514
514
515 owner = co.author.user_id == c.rhodecode_user.user_id
515 owner = co.author.user_id == c.rhodecode_user.user_id
516 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
516 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
517 ChangesetCommentsModel().delete(comment=co)
517 ChangesetCommentsModel().delete(comment=co)
518 Session().commit()
518 Session().commit()
519 return True
519 return True
520 else:
520 else:
521 raise HTTPForbidden()
521 raise HTTPForbidden()
@@ -1,146 +1,146 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 import urllib
27 import urllib
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 BaseRepoController, render
32 from rhodecode.lib.base import BaseRepoController, 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, safe_int
43 from rhodecode.lib.utils2 import safe_str, safe_int
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class SearchController(BaseRepoController):
49 class SearchController(BaseRepoController):
50
50
51 @LoginRequired()
51 @LoginRequired()
52 def __before__(self):
52 def __before__(self):
53 super(SearchController, self).__before__()
53 super(SearchController, self).__before__()
54
54
55 def index(self, repo_name=None):
55 def index(self, repo_name=None):
56 c.repo_name = repo_name
56 c.repo_name = repo_name
57 c.formated_results = []
57 c.formated_results = []
58 c.runtime = ''
58 c.runtime = ''
59 c.cur_query = request.GET.get('q', None)
59 c.cur_query = request.GET.get('q', None)
60 c.cur_type = request.GET.get('type', 'content')
60 c.cur_type = request.GET.get('type', 'content')
61 c.cur_search = search_type = {'content': 'content',
61 c.cur_search = search_type = {'content': 'content',
62 'commit': 'message',
62 'commit': 'message',
63 'path': 'path',
63 'path': 'path',
64 'repository': 'repository'
64 'repository': 'repository'
65 }.get(c.cur_type, 'content')
65 }.get(c.cur_type, 'content')
66
66
67 index_name = {
67 index_name = {
68 'content': IDX_NAME,
68 'content': IDX_NAME,
69 'commit': CHGSET_IDX_NAME,
69 'commit': CHGSET_IDX_NAME,
70 'path': IDX_NAME
70 'path': IDX_NAME
71 }.get(c.cur_type, IDX_NAME)
71 }.get(c.cur_type, IDX_NAME)
72
72
73 schema_defn = {
73 schema_defn = {
74 'content': SCHEMA,
74 'content': SCHEMA,
75 'commit': CHGSETS_SCHEMA,
75 'commit': CHGSETS_SCHEMA,
76 'path': SCHEMA
76 'path': SCHEMA
77 }.get(c.cur_type, SCHEMA)
77 }.get(c.cur_type, SCHEMA)
78
78
79 log.debug('IDX: %s' % index_name)
79 log.debug('IDX: %s' % index_name)
80 log.debug('SCHEMA: %s' % schema_defn)
80 log.debug('SCHEMA: %s' % schema_defn)
81
81
82 if c.cur_query:
82 if c.cur_query:
83 cur_query = c.cur_query.lower()
83 cur_query = c.cur_query.lower()
84 log.debug(cur_query)
84 log.debug(cur_query)
85
85
86 if c.cur_query:
86 if c.cur_query:
87 p = safe_int(request.params.get('page', 1), 1)
87 p = safe_int(request.GET.get('page', 1), 1)
88 highlight_items = set()
88 highlight_items = set()
89 try:
89 try:
90 idx = open_dir(config['app_conf']['index_dir'],
90 idx = open_dir(config['app_conf']['index_dir'],
91 indexname=index_name)
91 indexname=index_name)
92 searcher = idx.searcher()
92 searcher = idx.searcher()
93
93
94 qp = QueryParser(search_type, schema=schema_defn)
94 qp = QueryParser(search_type, schema=schema_defn)
95 if c.repo_name:
95 if c.repo_name:
96 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
96 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
97 try:
97 try:
98 query = qp.parse(unicode(cur_query))
98 query = qp.parse(unicode(cur_query))
99 # extract words for highlight
99 # extract words for highlight
100 if isinstance(query, Phrase):
100 if isinstance(query, Phrase):
101 highlight_items.update(query.words)
101 highlight_items.update(query.words)
102 elif isinstance(query, Prefix):
102 elif isinstance(query, Prefix):
103 highlight_items.add(query.text)
103 highlight_items.add(query.text)
104 else:
104 else:
105 for i in query.all_terms():
105 for i in query.all_terms():
106 if i[0] in ['content', 'message']:
106 if i[0] in ['content', 'message']:
107 highlight_items.add(i[1])
107 highlight_items.add(i[1])
108
108
109 matcher = query.matcher(searcher)
109 matcher = query.matcher(searcher)
110
110
111 log.debug('query: %s' % query)
111 log.debug('query: %s' % query)
112 log.debug('hl terms: %s' % highlight_items)
112 log.debug('hl terms: %s' % highlight_items)
113 results = searcher.search(query)
113 results = searcher.search(query)
114 res_ln = len(results)
114 res_ln = len(results)
115 c.runtime = '%s results (%.3f seconds)' % (
115 c.runtime = '%s results (%.3f seconds)' % (
116 res_ln, results.runtime
116 res_ln, results.runtime
117 )
117 )
118
118
119 def url_generator(**kw):
119 def url_generator(**kw):
120 q = urllib.quote(safe_str(c.cur_query))
120 q = urllib.quote(safe_str(c.cur_query))
121 return update_params("?q=%s&type=%s" \
121 return update_params("?q=%s&type=%s" \
122 % (q, safe_str(c.cur_type)), **kw)
122 % (q, safe_str(c.cur_type)), **kw)
123 repo_location = RepoModel().repos_path
123 repo_location = RepoModel().repos_path
124 c.formated_results = Page(
124 c.formated_results = Page(
125 WhooshResultWrapper(search_type, searcher, matcher,
125 WhooshResultWrapper(search_type, searcher, matcher,
126 highlight_items, repo_location),
126 highlight_items, repo_location),
127 page=p,
127 page=p,
128 item_count=res_ln,
128 item_count=res_ln,
129 items_per_page=10,
129 items_per_page=10,
130 url=url_generator
130 url=url_generator
131 )
131 )
132
132
133 except QueryParserError:
133 except QueryParserError:
134 c.runtime = _('Invalid search query. Try quoting it.')
134 c.runtime = _('Invalid search query. Try quoting it.')
135 searcher.close()
135 searcher.close()
136 except (EmptyIndexError, IOError):
136 except (EmptyIndexError, IOError):
137 log.error(traceback.format_exc())
137 log.error(traceback.format_exc())
138 log.error('Empty Index data')
138 log.error('Empty Index data')
139 c.runtime = _('There is no index to search in. '
139 c.runtime = _('There is no index to search in. '
140 'Please run whoosh indexer')
140 'Please run whoosh indexer')
141 except (Exception):
141 except (Exception):
142 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
143 c.runtime = _('An error occurred during this search operation')
143 c.runtime = _('An error occurred during this search operation')
144
144
145 # Return a rendered template
145 # Return a rendered template
146 return render('/search/search.html')
146 return render('/search/search.html')
@@ -1,107 +1,107 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 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.base import BaseRepoController, render
33 from rhodecode.lib.base import BaseRepoController, render
34 from rhodecode.lib.helpers import RepoPage
34 from rhodecode.lib.helpers import RepoPage
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36 from rhodecode.lib.utils2 import safe_int
36 from rhodecode.lib.utils2 import safe_int
37 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError, ChangesetError,\
37 from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError, ChangesetError,\
38 RepositoryError
38 RepositoryError
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class ShortlogController(BaseRepoController):
43 class ShortlogController(BaseRepoController):
44
44
45 @LoginRequired()
45 @LoginRequired()
46 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
46 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
47 'repository.admin')
47 'repository.admin')
48 def __before__(self):
48 def __before__(self):
49 super(ShortlogController, self).__before__()
49 super(ShortlogController, self).__before__()
50
50
51 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
51 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
52 """
52 """
53 Safe way to get changeset if error occur it redirects to tip with
53 Safe way to get changeset if error occur it redirects to tip with
54 proper message
54 proper message
55
55
56 :param rev: revision to fetch
56 :param rev: revision to fetch
57 :param repo_name: repo name to redirect after
57 :param repo_name: repo name to redirect after
58 """
58 """
59
59
60 try:
60 try:
61 return c.rhodecode_repo.get_changeset(rev)
61 return c.rhodecode_repo.get_changeset(rev)
62 except RepositoryError, e:
62 except RepositoryError, e:
63 h.flash(str(e), category='warning')
63 h.flash(str(e), category='warning')
64 redirect(h.url('shortlog_home', repo_name=repo_name))
64 redirect(h.url('shortlog_home', repo_name=repo_name))
65
65
66 def index(self, repo_name, revision=None, f_path=None):
66 def index(self, repo_name, revision=None, f_path=None):
67 p = safe_int(request.params.get('page', 1), 1)
67 p = safe_int(request.GET.get('page', 1), 1)
68 size = safe_int(request.params.get('size', 20), 20)
68 size = safe_int(request.GET.get('size', 20), 20)
69 collection = c.rhodecode_repo
69 collection = c.rhodecode_repo
70 c.file_history = f_path
70 c.file_history = f_path
71
71
72 def url_generator(**kw):
72 def url_generator(**kw):
73 if f_path:
73 if f_path:
74 return url('shortlog_file_home', repo_name=repo_name,
74 return url('shortlog_file_home', repo_name=repo_name,
75 revision=revision, f_path=f_path, size=size, **kw)
75 revision=revision, f_path=f_path, size=size, **kw)
76 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
76 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
77
77
78 if f_path:
78 if f_path:
79 log.debug('generating shortlog for path %s' % f_path)
79 log.debug('generating shortlog for path %s' % f_path)
80 # get the history for the file !
80 # get the history for the file !
81 tip_cs = c.rhodecode_repo.get_changeset()
81 tip_cs = c.rhodecode_repo.get_changeset()
82 try:
82 try:
83 collection = tip_cs.get_file_history(f_path)
83 collection = tip_cs.get_file_history(f_path)
84 except (NodeDoesNotExistError, ChangesetError):
84 except (NodeDoesNotExistError, ChangesetError):
85 #this node is not present at tip !
85 #this node is not present at tip !
86 try:
86 try:
87 cs = self.__get_cs_or_redirect(revision, repo_name)
87 cs = self.__get_cs_or_redirect(revision, repo_name)
88 collection = cs.get_file_history(f_path)
88 collection = cs.get_file_history(f_path)
89 except RepositoryError, e:
89 except RepositoryError, e:
90 h.flash(str(e), category='warning')
90 h.flash(str(e), category='warning')
91 redirect(h.url('shortlog_home', repo_name=repo_name))
91 redirect(h.url('shortlog_home', repo_name=repo_name))
92 collection = list(reversed(collection))
92 collection = list(reversed(collection))
93
93
94 c.repo_changesets = RepoPage(collection, page=p,
94 c.repo_changesets = RepoPage(collection, page=p,
95 items_per_page=size, url=url_generator)
95 items_per_page=size, url=url_generator)
96 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
96 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
97 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
97 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
98
98
99 if not c.repo_changesets:
99 if not c.repo_changesets:
100 h.flash(_('There are no changesets yet'), category='warning')
100 h.flash(_('There are no changesets yet'), category='warning')
101 return redirect(url('summary_home', repo_name=repo_name))
101 return redirect(url('summary_home', repo_name=repo_name))
102
102
103 c.shortlog_data = render('shortlog/shortlog_data.html')
103 c.shortlog_data = render('shortlog/shortlog_data.html')
104 if request.environ.get('HTTP_X_PARTIAL_XHR'):
104 if request.environ.get('HTTP_X_PARTIAL_XHR'):
105 return c.shortlog_data
105 return c.shortlog_data
106 r = render('shortlog/shortlog.html')
106 r = render('shortlog/shortlog.html')
107 return r
107 return r
General Comments 0
You need to be logged in to leave comments. Login now