##// END OF EJS Templates
journal: ported controller to pyramid code
marcink -
r1933:b6b05465 default
parent child Browse files
Show More
@@ -0,0 +1,53 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from rhodecode.apps._base import ADMIN_PREFIX
23
24
25 def admin_routes(config):
26
27 config.add_route(
28 name='journal', pattern='/journal')
29 config.add_route(
30 name='journal_rss', pattern='/journal/rss')
31 config.add_route(
32 name='journal_atom', pattern='/journal/atom')
33
34 config.add_route(
35 name='journal_public', pattern='/public_journal')
36 config.add_route(
37 name='journal_public_atom', pattern='/public_journal/atom')
38 config.add_route(
39 name='journal_public_atom_old', pattern='/public_journal_atom')
40
41 config.add_route(
42 name='journal_public_rss', pattern='/public_journal/rss')
43 config.add_route(
44 name='journal_public_rss_old', pattern='/public_journal_rss')
45
46 config.add_route(
47 name='toggle_following', pattern='/toggle_following')
48
49
50 def includeme(config):
51 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
52 # Scan module for configuration decorators.
53 config.scan() No newline at end of file
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,377 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import logging
23 import itertools
24
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26
27 from pyramid.view import view_config
28 from pyramid.httpexceptions import HTTPBadRequest
29 from pyramid.response import Response
30 from pyramid.renderers import render
31
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.model.db import (
34 or_, joinedload, UserLog, UserFollowing, User, UserApiKeys)
35 from rhodecode.model.meta import Session
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
40 from rhodecode.lib.utils2 import safe_int, AttributeDict
41 from rhodecode.model.scm import ScmModel
42
43 log = logging.getLogger(__name__)
44
45
46 class JournalView(BaseAppView):
47
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 self._register_global_c(c)
51 self._load_defaults(c.rhodecode_name)
52
53 # TODO(marcink): what is this, why we need a global register ?
54 c.search_term = self.request.GET.get('filter') or ''
55 return c
56
57 def _get_config(self, rhodecode_name):
58 import rhodecode
59 config = rhodecode.CONFIG
60
61 return {
62 'language': 'en-us',
63 'feed_ttl': '5', # TTL of feed,
64 'feed_items_per_page':
65 safe_int(config.get('rss_items_per_page', 20)),
66 'rhodecode_name': rhodecode_name
67 }
68
69 def _load_defaults(self, rhodecode_name):
70 config = self._get_config(rhodecode_name)
71 # common values for feeds
72 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
74 self.feed_items_per_page = config['feed_items_per_page']
75 self.rhodecode_name = config['rhodecode_name']
76
77 def _get_daily_aggregate(self, journal):
78 groups = []
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 user_group = []
81 # groupby username if it's a present value, else
82 # fallback to journal username
83 for _, g2 in itertools.groupby(
84 list(g), lambda x: x.user.username if x.user else x.username):
85 l = list(g2)
86 user_group.append((l[0].user, l))
87
88 groups.append((k, user_group,))
89
90 return groups
91
92 def _get_journal_data(self, following_repos, search_term):
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 if x.follows_repository is not None]
95 user_ids = [x.follows_user.user_id for x in following_repos
96 if x.follows_user is not None]
97
98 filtering_criterion = None
99
100 if repo_ids and user_ids:
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 UserLog.user_id.in_(user_ids))
103 if repo_ids and not user_ids:
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 if not repo_ids and user_ids:
106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 if filtering_criterion is not None:
108 journal = Session().query(UserLog)\
109 .options(joinedload(UserLog.user))\
110 .options(joinedload(UserLog.repository))
111 # filter
112 try:
113 journal = user_log_filter(journal, search_term)
114 except Exception:
115 # we want this to crash for now
116 raise
117 journal = journal.filter(filtering_criterion)\
118 .order_by(UserLog.action_date.desc())
119 else:
120 journal = []
121
122 return journal
123
124 def _atom_feed(self, repos, search_term, public=True):
125 _ = self.request.translate
126 journal = self._get_journal_data(repos, search_term)
127 if public:
128 _link = h.route_url('journal_public_atom')
129 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
130 'atom feed')
131 else:
132 _link = h.route_url('journal_atom')
133 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
134
135 feed = Atom1Feed(
136 title=_desc, link=_link, description=_desc,
137 language=self.language, ttl=self.ttl)
138
139 for entry in journal[:self.feed_items_per_page]:
140 user = entry.user
141 if user is None:
142 # fix deleted users
143 user = AttributeDict({'short_contact': entry.username,
144 'email': '',
145 'full_contact': ''})
146 action, action_extra, ico = h.action_parser(entry, feed=True)
147 title = "%s - %s %s" % (user.short_contact, action(),
148 entry.repository.repo_name)
149 desc = action_extra()
150 _url = h.route_url('home')
151 if entry.repository is not None:
152 _url = h.route_url('repo_changelog',
153 repo_name=entry.repository.repo_name)
154
155 feed.add_item(title=title,
156 pubdate=entry.action_date,
157 link=_url,
158 author_email=user.email,
159 author_name=user.full_contact,
160 description=desc)
161
162 response = Response(feed.writeString('utf-8'))
163 response.content_type = feed.mime_type
164 return response
165
166 def _rss_feed(self, repos, search_term, public=True):
167 _ = self.request.translate
168 journal = self._get_journal_data(repos, search_term)
169 if public:
170 _link = h.route_url('journal_public_atom')
171 _desc = '%s %s %s' % (
172 self.rhodecode_name, _('public journal'), 'rss feed')
173 else:
174 _link = h.route_url('journal_atom')
175 _desc = '%s %s %s' % (
176 self.rhodecode_name, _('journal'), 'rss feed')
177
178 feed = Rss201rev2Feed(
179 title=_desc, link=_link, description=_desc,
180 language=self.language, ttl=self.ttl)
181
182 for entry in journal[:self.feed_items_per_page]:
183 user = entry.user
184 if user is None:
185 # fix deleted users
186 user = AttributeDict({'short_contact': entry.username,
187 'email': '',
188 'full_contact': ''})
189 action, action_extra, ico = h.action_parser(entry, feed=True)
190 title = "%s - %s %s" % (user.short_contact, action(),
191 entry.repository.repo_name)
192 desc = action_extra()
193 _url = h.route_url('home')
194 if entry.repository is not None:
195 _url = h.route_url('repo_changelog',
196 repo_name=entry.repository.repo_name)
197
198 feed.add_item(title=title,
199 pubdate=entry.action_date,
200 link=_url,
201 author_email=user.email,
202 author_name=user.full_contact,
203 description=desc)
204
205 response = Response(feed.writeString('utf-8'))
206 response.content_type = feed.mime_type
207 return response
208
209 @LoginRequired()
210 @NotAnonymous()
211 @view_config(
212 route_name='journal', request_method='GET',
213 renderer=None)
214 def journal(self):
215 c = self.load_default_context()
216
217 p = safe_int(self.request.GET.get('page', 1), 1)
218 c.user = User.get(self._rhodecode_user.user_id)
219 following = Session().query(UserFollowing)\
220 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
221 .options(joinedload(UserFollowing.follows_repository))\
222 .all()
223
224 journal = self._get_journal_data(following, c.search_term)
225
226 def url_generator(**kw):
227 query_params = {
228 'filter': c.search_term
229 }
230 query_params.update(kw)
231 return self.request.current_route_path(_query=query_params)
232
233 c.journal_pager = Page(
234 journal, page=p, items_per_page=20, url=url_generator)
235 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
236
237 c.journal_data = render(
238 'rhodecode:templates/journal/journal_data.mako',
239 self._get_template_context(c), self.request)
240
241 if self.request.is_xhr:
242 return Response(c.journal_data)
243
244 html = render(
245 'rhodecode:templates/journal/journal.mako',
246 self._get_template_context(c), self.request)
247 return Response(html)
248
249 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
250 @NotAnonymous()
251 @view_config(
252 route_name='journal_atom', request_method='GET',
253 renderer=None)
254 def journal_atom(self):
255 """
256 Produce an atom-1.0 feed via feedgenerator module
257 """
258 c = self.load_default_context()
259 following_repos = Session().query(UserFollowing)\
260 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
261 .options(joinedload(UserFollowing.follows_repository))\
262 .all()
263 return self._atom_feed(following_repos, c.search_term, public=False)
264
265 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
266 @NotAnonymous()
267 @view_config(
268 route_name='journal_rss', request_method='GET',
269 renderer=None)
270 def journal_rss(self):
271 """
272 Produce an rss feed via feedgenerator module
273 """
274 c = self.load_default_context()
275 following_repos = Session().query(UserFollowing)\
276 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
277 .options(joinedload(UserFollowing.follows_repository))\
278 .all()
279 return self._rss_feed(following_repos, c.search_term, public=False)
280
281 @LoginRequired()
282 @NotAnonymous()
283 @CSRFRequired()
284 @view_config(
285 route_name='toggle_following', request_method='POST',
286 renderer='json_ext')
287 def toggle_following(self):
288 user_id = self.request.POST.get('follows_user_id')
289 if user_id:
290 try:
291 ScmModel().toggle_following_user(
292 user_id, self._rhodecode_user.user_id)
293 Session().commit()
294 return 'ok'
295 except Exception:
296 raise HTTPBadRequest()
297
298 repo_id = self.request.POST.get('follows_repo_id')
299 if repo_id:
300 try:
301 ScmModel().toggle_following_repo(
302 repo_id, self._rhodecode_user.user_id)
303 Session().commit()
304 return 'ok'
305 except Exception:
306 raise HTTPBadRequest()
307
308 raise HTTPBadRequest()
309
310 @LoginRequired()
311 @view_config(
312 route_name='journal_public', request_method='GET',
313 renderer=None)
314 def journal_public(self):
315 c = self.load_default_context()
316 # Return a rendered template
317 p = safe_int(self.request.GET.get('page', 1), 1)
318
319 c.following = Session().query(UserFollowing)\
320 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
321 .options(joinedload(UserFollowing.follows_repository))\
322 .all()
323
324 journal = self._get_journal_data(c.following, c.search_term)
325
326 def url_generator(**kw):
327 query_params = {}
328 query_params.update(kw)
329 return self.request.current_route_path(_query=query_params)
330
331 c.journal_pager = Page(
332 journal, page=p, items_per_page=20, url=url_generator)
333 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
334
335 c.journal_data = render(
336 'rhodecode:templates/journal/journal_data.mako',
337 self._get_template_context(c), self.request)
338
339 if self.request.is_xhr:
340 return Response(c.journal_data)
341
342 html = render(
343 'rhodecode:templates/journal/public_journal.mako',
344 self._get_template_context(c), self.request)
345 return Response(html)
346
347 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
348 @view_config(
349 route_name='journal_public_atom', request_method='GET',
350 renderer=None)
351 def journal_public_atom(self):
352 """
353 Produce an atom-1.0 feed via feedgenerator module
354 """
355 c = self.load_default_context()
356 following_repos = Session().query(UserFollowing)\
357 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
358 .options(joinedload(UserFollowing.follows_repository))\
359 .all()
360
361 return self._atom_feed(following_repos, c.search_term)
362
363 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
364 @view_config(
365 route_name='journal_public_rss', request_method='GET',
366 renderer=None)
367 def journal_public_rss(self):
368 """
369 Produce an rss2 feed via feedgenerator module
370 """
371 c = self.load_default_context()
372 following_repos = Session().query(UserFollowing)\
373 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
374 .options(joinedload(UserFollowing.follows_repository))\
375 .all()
376
377 return self._rss_feed(following_repos, c.search_term)
@@ -1,64 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import datetime
22 from rhodecode.tests import TestController, url
22
23 import pytest
24
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.tests import TestController
23 27 from rhodecode.model.db import UserFollowing, Repository
24 28
25 29
26 class TestJournalController(TestController):
30 def route_path(name, params=None, **kwargs):
31 import urllib
27 32
28 def test_index(self):
33 base_url = {
34 'journal': ADMIN_PREFIX + '/journal',
35 'journal_rss': ADMIN_PREFIX + '/journal/rss',
36 'journal_atom': ADMIN_PREFIX + '/journal/atom',
37 'journal_public': ADMIN_PREFIX + '/public_journal',
38 'journal_public_atom': ADMIN_PREFIX + '/public_journal/atom',
39 'journal_public_atom_old': ADMIN_PREFIX + '/public_journal_atom',
40 'journal_public_rss': ADMIN_PREFIX + '/public_journal/rss',
41 'journal_public_rss_old': ADMIN_PREFIX + '/public_journal_rss',
42 'toggle_following': ADMIN_PREFIX + '/toggle_following',
43 }[name].format(**kwargs)
44
45 if params:
46 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
47 return base_url
48
49
50 class TestJournalViews(TestController):
51
52 def test_journal(self):
29 53 self.log_user()
30 response = self.app.get(url(controller='journal', action='index'))
31 response.mustcontain(
32 """<div class="journal_day">%s</div>""" % datetime.date.today())
54 response = self.app.get(route_path('journal'))
55 # response.mustcontain(
56 # """<div class="journal_day">%s</div>""" % datetime.date.today())
57
58 @pytest.mark.parametrize("feed_type, content_type", [
59 ('rss', "application/rss+xml"),
60 ('atom', "application/atom+xml")
61 ])
62 def test_journal_feed(self, feed_type, content_type):
63 self.log_user()
64 response = self.app.get(
65 route_path(
66 'journal_{}'.format(feed_type)),
67 status=200)
68
69 assert response.content_type == content_type
33 70
34 71 def test_toggle_following_repository(self, backend):
35 72 user = self.log_user()
36 73 repo = Repository.get_by_repo_name(backend.repo_name)
37 74 repo_id = repo.repo_id
38 self.app.post(url('toggle_following'), {'follows_repo_id': repo_id,
39 'csrf_token': self.csrf_token})
75 self.app.post(
76 route_path('toggle_following'), {'follows_repo_id': repo_id,
77 'csrf_token': self.csrf_token})
40 78
41 79 followings = UserFollowing.query()\
42 80 .filter(UserFollowing.user_id == user['user_id'])\
43 81 .filter(UserFollowing.follows_repo_id == repo_id).all()
44 82
45 83 assert len(followings) == 0
46 84
47 self.app.post(url('toggle_following'), {'follows_repo_id': repo_id,
48 'csrf_token': self.csrf_token})
85 self.app.post(
86 route_path('toggle_following'), {'follows_repo_id': repo_id,
87 'csrf_token': self.csrf_token})
49 88
50 89 followings = UserFollowing.query()\
51 90 .filter(UserFollowing.user_id == user['user_id'])\
52 91 .filter(UserFollowing.follows_repo_id == repo_id).all()
53 92
54 93 assert len(followings) == 1
55 94
56 def test_public_journal_atom(self):
95 @pytest.mark.parametrize("feed_type, content_type", [
96 ('rss', "application/rss+xml"),
97 ('atom', "application/atom+xml")
98 ])
99 def test_public_journal_feed(self, feed_type, content_type):
57 100 self.log_user()
58 response = self.app.get(url(controller='journal',
59 action='public_journal_atom'),)
101 response = self.app.get(
102 route_path(
103 'journal_public_{}'.format(feed_type)),
104 status=200)
60 105
61 def test_public_journal_rss(self):
62 self.log_user()
63 response = self.app.get(url(controller='journal',
64 action='public_journal_rss'),)
106 assert response.content_type == content_type
@@ -1,528 +1,529 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 import traceback
26 26 from collections import OrderedDict
27 27
28 28 from paste.registry import RegistryManager
29 29 from paste.gzipper import make_gzip_middleware
30 30 from pylons.wsgiapp import PylonsApp
31 31 from pyramid.authorization import ACLAuthorizationPolicy
32 32 from pyramid.config import Configurator
33 33 from pyramid.settings import asbool, aslist
34 34 from pyramid.wsgi import wsgiapp
35 35 from pyramid.httpexceptions import (
36 36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
37 37 from pyramid.events import ApplicationCreated
38 38 from pyramid.renderers import render_to_response
39 39 from routes.middleware import RoutesMiddleware
40 40 import rhodecode
41 41
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47
48 48 from rhodecode.lib.vcs import VCSCommunicationError
49 49 from rhodecode.lib.exceptions import VCSServerUnavailable
50 50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 51 from rhodecode.lib.middleware.error_handling import (
52 52 PylonsErrorHandlingMiddleware)
53 53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
54 54 from rhodecode.lib.middleware.vcs import VCSMiddleware
55 55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
56 56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
57 57 from rhodecode.subscribers import (
58 58 scan_repositories_if_enabled, write_js_routes_if_enabled,
59 59 write_metadata_if_needed)
60 60
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
66 66 # for certain routes which won't go to pylons to - eg. static files, debugger
67 67 # it is only needed for the pylons migration and can be removed once complete
68 68 class SkippableRoutesMiddleware(RoutesMiddleware):
69 69 """ Routes middleware that allows you to skip prefixes """
70 70
71 71 def __init__(self, *args, **kw):
72 72 self.skip_prefixes = kw.pop('skip_prefixes', [])
73 73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
74 74
75 75 def __call__(self, environ, start_response):
76 76 for prefix in self.skip_prefixes:
77 77 if environ['PATH_INFO'].startswith(prefix):
78 78 # added to avoid the case when a missing /_static route falls
79 79 # through to pylons and causes an exception as pylons is
80 80 # expecting wsgiorg.routingargs to be set in the environ
81 81 # by RoutesMiddleware.
82 82 if 'wsgiorg.routing_args' not in environ:
83 83 environ['wsgiorg.routing_args'] = (None, {})
84 84 return self.app(environ, start_response)
85 85
86 86 return super(SkippableRoutesMiddleware, self).__call__(
87 87 environ, start_response)
88 88
89 89
90 90 def make_app(global_conf, static_files=True, **app_conf):
91 91 """Create a Pylons WSGI application and return it
92 92
93 93 ``global_conf``
94 94 The inherited configuration for this application. Normally from
95 95 the [DEFAULT] section of the Paste ini file.
96 96
97 97 ``app_conf``
98 98 The application's local configuration. Normally specified in
99 99 the [app:<name>] section of the Paste ini file (where <name>
100 100 defaults to main).
101 101
102 102 """
103 103 # Apply compatibility patches
104 104 patches.kombu_1_5_1_python_2_7_11()
105 105 patches.inspect_getargspec()
106 106
107 107 # Configure the Pylons environment
108 108 config = load_environment(global_conf, app_conf)
109 109
110 110 # The Pylons WSGI app
111 111 app = PylonsApp(config=config)
112 112
113 113 # Establish the Registry for this application
114 114 app = RegistryManager(app)
115 115
116 116 app.config = config
117 117
118 118 return app
119 119
120 120
121 121 def make_pyramid_app(global_config, **settings):
122 122 """
123 123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
124 124 application.
125 125
126 126 Specials:
127 127
128 128 * We migrate from Pylons to Pyramid. While doing this, we keep both
129 129 frameworks functional. This involves moving some WSGI middlewares around
130 130 and providing access to some data internals, so that the old code is
131 131 still functional.
132 132
133 133 * The application can also be integrated like a plugin via the call to
134 134 `includeme`. This is accompanied with the other utility functions which
135 135 are called. Changing this should be done with great care to not break
136 136 cases when these fragments are assembled from another place.
137 137
138 138 """
139 139 # The edition string should be available in pylons too, so we add it here
140 140 # before copying the settings.
141 141 settings.setdefault('rhodecode.edition', 'Community Edition')
142 142
143 143 # As long as our Pylons application does expect "unprepared" settings, make
144 144 # sure that we keep an unmodified copy. This avoids unintentional change of
145 145 # behavior in the old application.
146 146 settings_pylons = settings.copy()
147 147
148 148 sanitize_settings_and_apply_defaults(settings)
149 149 config = Configurator(settings=settings)
150 150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
151 151
152 152 load_pyramid_environment(global_config, settings)
153 153
154 154 includeme_first(config)
155 155 includeme(config)
156 156
157 157 pyramid_app = config.make_wsgi_app()
158 158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
159 159 pyramid_app.config = config
160 160
161 161 # creating the app uses a connection - return it after we are done
162 162 meta.Session.remove()
163 163
164 164 return pyramid_app
165 165
166 166
167 167 def make_not_found_view(config):
168 168 """
169 169 This creates the view which should be registered as not-found-view to
170 170 pyramid. Basically it contains of the old pylons app, converted to a view.
171 171 Additionally it is wrapped by some other middlewares.
172 172 """
173 173 settings = config.registry.settings
174 174 vcs_server_enabled = settings['vcs.server.enable']
175 175
176 176 # Make pylons app from unprepared settings.
177 177 pylons_app = make_app(
178 178 config.registry._pylons_compat_global_config,
179 179 **config.registry._pylons_compat_settings)
180 180 config.registry._pylons_compat_config = pylons_app.config
181 181
182 182 # Appenlight monitoring.
183 183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
184 184 pylons_app, settings)
185 185
186 186 # The pylons app is executed inside of the pyramid 404 exception handler.
187 187 # Exceptions which are raised inside of it are not handled by pyramid
188 188 # again. Therefore we add a middleware that invokes the error handler in
189 189 # case of an exception or error response. This way we return proper error
190 190 # HTML pages in case of an error.
191 191 reraise = (settings.get('debugtoolbar.enabled', False) or
192 192 rhodecode.disable_error_handler)
193 193 pylons_app = PylonsErrorHandlingMiddleware(
194 194 pylons_app, error_handler, reraise)
195 195
196 196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
197 197 # view to handle the request. Therefore it is wrapped around the pylons
198 198 # app. It has to be outside of the error handling otherwise error responses
199 199 # from the vcsserver are converted to HTML error pages. This confuses the
200 200 # command line tools and the user won't get a meaningful error message.
201 201 if vcs_server_enabled:
202 202 pylons_app = VCSMiddleware(
203 203 pylons_app, settings, appenlight_client, registry=config.registry)
204 204
205 205 # Convert WSGI app to pyramid view and return it.
206 206 return wsgiapp(pylons_app)
207 207
208 208
209 209 def add_pylons_compat_data(registry, global_config, settings):
210 210 """
211 211 Attach data to the registry to support the Pylons integration.
212 212 """
213 213 registry._pylons_compat_global_config = global_config
214 214 registry._pylons_compat_settings = settings
215 215
216 216
217 217 def error_handler(exception, request):
218 218 import rhodecode
219 219 from rhodecode.lib import helpers
220 220
221 221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
222 222
223 223 base_response = HTTPInternalServerError()
224 224 # prefer original exception for the response since it may have headers set
225 225 if isinstance(exception, HTTPException):
226 226 base_response = exception
227 227 elif isinstance(exception, VCSCommunicationError):
228 228 base_response = VCSServerUnavailable()
229 229
230 230 def is_http_error(response):
231 231 # error which should have traceback
232 232 return response.status_code > 499
233 233
234 234 if is_http_error(base_response):
235 235 log.exception(
236 236 'error occurred handling this request for path: %s', request.path)
237 237
238 238 c = AttributeDict()
239 239 c.error_message = base_response.status
240 240 c.error_explanation = base_response.explanation or str(base_response)
241 241 c.visual = AttributeDict()
242 242
243 243 c.visual.rhodecode_support_url = (
244 244 request.registry.settings.get('rhodecode_support_url') or
245 245 request.route_url('rhodecode_support')
246 246 )
247 247 c.redirect_time = 0
248 248 c.rhodecode_name = rhodecode_title
249 249 if not c.rhodecode_name:
250 250 c.rhodecode_name = 'Rhodecode'
251 251
252 252 c.causes = []
253 253 if hasattr(base_response, 'causes'):
254 254 c.causes = base_response.causes
255 255 c.messages = helpers.flash.pop_messages(request=request)
256 256 c.traceback = traceback.format_exc()
257 257 response = render_to_response(
258 258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
259 259 response=base_response)
260 260
261 261 return response
262 262
263 263
264 264 def includeme(config):
265 265 settings = config.registry.settings
266 266
267 267 # plugin information
268 268 config.registry.rhodecode_plugins = OrderedDict()
269 269
270 270 config.add_directive(
271 271 'register_rhodecode_plugin', register_rhodecode_plugin)
272 272
273 273 if asbool(settings.get('appenlight', 'false')):
274 274 config.include('appenlight_client.ext.pyramid_tween')
275 275
276 276 # Includes which are required. The application would fail without them.
277 277 config.include('pyramid_mako')
278 278 config.include('pyramid_beaker')
279 279
280 280 config.include('rhodecode.authentication')
281 281 config.include('rhodecode.integrations')
282 282
283 283 # apps
284 284 config.include('rhodecode.apps._base')
285 285 config.include('rhodecode.apps.ops')
286 286
287 287 config.include('rhodecode.apps.admin')
288 288 config.include('rhodecode.apps.channelstream')
289 289 config.include('rhodecode.apps.login')
290 290 config.include('rhodecode.apps.home')
291 config.include('rhodecode.apps.journal')
291 292 config.include('rhodecode.apps.repository')
292 293 config.include('rhodecode.apps.repo_group')
293 294 config.include('rhodecode.apps.search')
294 295 config.include('rhodecode.apps.user_profile')
295 296 config.include('rhodecode.apps.my_account')
296 297 config.include('rhodecode.apps.svn_support')
297 298 config.include('rhodecode.apps.gist')
298 299
299 300 config.include('rhodecode.apps.debug_style')
300 301 config.include('rhodecode.tweens')
301 302 config.include('rhodecode.api')
302 303
303 304 config.add_route(
304 305 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
305 306
306 307 config.add_translation_dirs('rhodecode:i18n/')
307 308 settings['default_locale_name'] = settings.get('lang', 'en')
308 309
309 310 # Add subscribers.
310 311 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
311 312 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
312 313 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
313 314
314 315 config.add_request_method(
315 316 'rhodecode.lib.partial_renderer.get_partial_renderer',
316 317 'get_partial_renderer')
317 318
318 319 # events
319 320 # TODO(marcink): this should be done when pyramid migration is finished
320 321 # config.add_subscriber(
321 322 # 'rhodecode.integrations.integrations_event_handler',
322 323 # 'rhodecode.events.RhodecodeEvent')
323 324
324 325 # Set the authorization policy.
325 326 authz_policy = ACLAuthorizationPolicy()
326 327 config.set_authorization_policy(authz_policy)
327 328
328 329 # Set the default renderer for HTML templates to mako.
329 330 config.add_mako_renderer('.html')
330 331
331 332 config.add_renderer(
332 333 name='json_ext',
333 334 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
334 335
335 336 # include RhodeCode plugins
336 337 includes = aslist(settings.get('rhodecode.includes', []))
337 338 for inc in includes:
338 339 config.include(inc)
339 340
340 341 # This is the glue which allows us to migrate in chunks. By registering the
341 342 # pylons based application as the "Not Found" view in Pyramid, we will
342 343 # fallback to the old application each time the new one does not yet know
343 344 # how to handle a request.
344 345 config.add_notfound_view(make_not_found_view(config))
345 346
346 347 if not settings.get('debugtoolbar.enabled', False):
347 348 # disabled debugtoolbar handle all exceptions via the error_handlers
348 349 config.add_view(error_handler, context=Exception)
349 350
350 351 config.add_view(error_handler, context=HTTPError)
351 352
352 353
353 354 def includeme_first(config):
354 355 # redirect automatic browser favicon.ico requests to correct place
355 356 def favicon_redirect(context, request):
356 357 return HTTPFound(
357 358 request.static_path('rhodecode:public/images/favicon.ico'))
358 359
359 360 config.add_view(favicon_redirect, route_name='favicon')
360 361 config.add_route('favicon', '/favicon.ico')
361 362
362 363 def robots_redirect(context, request):
363 364 return HTTPFound(
364 365 request.static_path('rhodecode:public/robots.txt'))
365 366
366 367 config.add_view(robots_redirect, route_name='robots')
367 368 config.add_route('robots', '/robots.txt')
368 369
369 370 config.add_static_view(
370 371 '_static/deform', 'deform:static')
371 372 config.add_static_view(
372 373 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
373 374
374 375
375 376 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
376 377 """
377 378 Apply outer WSGI middlewares around the application.
378 379
379 380 Part of this has been moved up from the Pylons layer, so that the
380 381 data is also available if old Pylons code is hit through an already ported
381 382 view.
382 383 """
383 384 settings = config.registry.settings
384 385
385 386 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
386 387 pyramid_app = HttpsFixup(pyramid_app, settings)
387 388
388 389 # Add RoutesMiddleware to support the pylons compatibility tween during
389 390 # migration to pyramid.
390 391
391 392 # TODO(marcink): remove after migration to pyramid
392 393 if hasattr(config.registry, '_pylons_compat_config'):
393 394 routes_map = config.registry._pylons_compat_config['routes.map']
394 395 pyramid_app = SkippableRoutesMiddleware(
395 396 pyramid_app, routes_map,
396 397 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
397 398
398 399 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
399 400
400 401 if settings['gzip_responses']:
401 402 pyramid_app = make_gzip_middleware(
402 403 pyramid_app, settings, compress_level=1)
403 404
404 405 # this should be the outer most middleware in the wsgi stack since
405 406 # middleware like Routes make database calls
406 407 def pyramid_app_with_cleanup(environ, start_response):
407 408 try:
408 409 return pyramid_app(environ, start_response)
409 410 finally:
410 411 # Dispose current database session and rollback uncommitted
411 412 # transactions.
412 413 meta.Session.remove()
413 414
414 415 # In a single threaded mode server, on non sqlite db we should have
415 416 # '0 Current Checked out connections' at the end of a request,
416 417 # if not, then something, somewhere is leaving a connection open
417 418 pool = meta.Base.metadata.bind.engine.pool
418 419 log.debug('sa pool status: %s', pool.status())
419 420
420 421 return pyramid_app_with_cleanup
421 422
422 423
423 424 def sanitize_settings_and_apply_defaults(settings):
424 425 """
425 426 Applies settings defaults and does all type conversion.
426 427
427 428 We would move all settings parsing and preparation into this place, so that
428 429 we have only one place left which deals with this part. The remaining parts
429 430 of the application would start to rely fully on well prepared settings.
430 431
431 432 This piece would later be split up per topic to avoid a big fat monster
432 433 function.
433 434 """
434 435
435 436 # Pyramid's mako renderer has to search in the templates folder so that the
436 437 # old templates still work. Ported and new templates are expected to use
437 438 # real asset specifications for the includes.
438 439 mako_directories = settings.setdefault('mako.directories', [
439 440 # Base templates of the original Pylons application
440 441 'rhodecode:templates',
441 442 ])
442 443 log.debug(
443 444 "Using the following Mako template directories: %s",
444 445 mako_directories)
445 446
446 447 # Default includes, possible to change as a user
447 448 pyramid_includes = settings.setdefault('pyramid.includes', [
448 449 'rhodecode.lib.middleware.request_wrapper',
449 450 ])
450 451 log.debug(
451 452 "Using the following pyramid.includes: %s",
452 453 pyramid_includes)
453 454
454 455 # TODO: johbo: Re-think this, usually the call to config.include
455 456 # should allow to pass in a prefix.
456 457 settings.setdefault('rhodecode.api.url', '/_admin/api')
457 458
458 459 # Sanitize generic settings.
459 460 _list_setting(settings, 'default_encoding', 'UTF-8')
460 461 _bool_setting(settings, 'is_test', 'false')
461 462 _bool_setting(settings, 'gzip_responses', 'false')
462 463
463 464 # Call split out functions that sanitize settings for each topic.
464 465 _sanitize_appenlight_settings(settings)
465 466 _sanitize_vcs_settings(settings)
466 467
467 468 return settings
468 469
469 470
470 471 def _sanitize_appenlight_settings(settings):
471 472 _bool_setting(settings, 'appenlight', 'false')
472 473
473 474
474 475 def _sanitize_vcs_settings(settings):
475 476 """
476 477 Applies settings defaults and does type conversion for all VCS related
477 478 settings.
478 479 """
479 480 _string_setting(settings, 'vcs.svn.compatible_version', '')
480 481 _string_setting(settings, 'git_rev_filter', '--all')
481 482 _string_setting(settings, 'vcs.hooks.protocol', 'http')
482 483 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
483 484 _string_setting(settings, 'vcs.server', '')
484 485 _string_setting(settings, 'vcs.server.log_level', 'debug')
485 486 _string_setting(settings, 'vcs.server.protocol', 'http')
486 487 _bool_setting(settings, 'startup.import_repos', 'false')
487 488 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
488 489 _bool_setting(settings, 'vcs.server.enable', 'true')
489 490 _bool_setting(settings, 'vcs.start_server', 'false')
490 491 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
491 492 _int_setting(settings, 'vcs.connection_timeout', 3600)
492 493
493 494 # Support legacy values of vcs.scm_app_implementation. Legacy
494 495 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
495 496 # which is now mapped to 'http'.
496 497 scm_app_impl = settings['vcs.scm_app_implementation']
497 498 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
498 499 settings['vcs.scm_app_implementation'] = 'http'
499 500
500 501
501 502 def _int_setting(settings, name, default):
502 503 settings[name] = int(settings.get(name, default))
503 504
504 505
505 506 def _bool_setting(settings, name, default):
506 507 input = settings.get(name, default)
507 508 if isinstance(input, unicode):
508 509 input = input.encode('utf8')
509 510 settings[name] = asbool(input)
510 511
511 512
512 513 def _list_setting(settings, name, default):
513 514 raw_value = settings.get(name, default)
514 515
515 516 old_separator = ','
516 517 if old_separator in raw_value:
517 518 # If we get a comma separated list, pass it to our own function.
518 519 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
519 520 else:
520 521 # Otherwise we assume it uses pyramids space/newline separation.
521 522 settings[name] = aslist(raw_value)
522 523
523 524
524 525 def _string_setting(settings, name, default, lower=True):
525 526 value = settings.get(name, default)
526 527 if lower:
527 528 value = value.lower()
528 529 settings[name] = value
@@ -1,715 +1,686 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 # prefix for non repository related links needs to be prefixed with `/`
36 36 ADMIN_PREFIX = '/_admin'
37 37 STATIC_FILE_PREFIX = '/_static'
38 38
39 39 # Default requirements for URL parts
40 40 URL_NAME_REQUIREMENTS = {
41 41 # group name can have a slash in them, but they must not end with a slash
42 42 'group_name': r'.*?[^/]',
43 43 'repo_group_name': r'.*?[^/]',
44 44 # repo names can have a slash in them, but they must not end with a slash
45 45 'repo_name': r'.*?[^/]',
46 46 # file path eats up everything at the end
47 47 'f_path': r'.*',
48 48 # reference types
49 49 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
50 50 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
51 51 }
52 52
53 53
54 54 class JSRoutesMapper(Mapper):
55 55 """
56 56 Wrapper for routes.Mapper to make pyroutes compatible url definitions
57 57 """
58 58 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
59 59 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
60 60 def __init__(self, *args, **kw):
61 61 super(JSRoutesMapper, self).__init__(*args, **kw)
62 62 self._jsroutes = []
63 63
64 64 def connect(self, *args, **kw):
65 65 """
66 66 Wrapper for connect to take an extra argument jsroute=True
67 67
68 68 :param jsroute: boolean, if True will add the route to the pyroutes list
69 69 """
70 70 if kw.pop('jsroute', False):
71 71 if not self._named_route_regex.match(args[0]):
72 72 raise Exception('only named routes can be added to pyroutes')
73 73 self._jsroutes.append(args[0])
74 74
75 75 super(JSRoutesMapper, self).connect(*args, **kw)
76 76
77 77 def _extract_route_information(self, route):
78 78 """
79 79 Convert a route into tuple(name, path, args), eg:
80 80 ('show_user', '/profile/%(username)s', ['username'])
81 81 """
82 82 routepath = route.routepath
83 83 def replace(matchobj):
84 84 if matchobj.group(1):
85 85 return "%%(%s)s" % matchobj.group(1).split(':')[0]
86 86 else:
87 87 return "%%(%s)s" % matchobj.group(2)
88 88
89 89 routepath = self._argument_prog.sub(replace, routepath)
90 90 return (
91 91 route.name,
92 92 routepath,
93 93 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
94 94 for arg in self._argument_prog.findall(route.routepath)]
95 95 )
96 96
97 97 def jsroutes(self):
98 98 """
99 99 Return a list of pyroutes.js compatible routes
100 100 """
101 101 for route_name in self._jsroutes:
102 102 yield self._extract_route_information(self._routenames[route_name])
103 103
104 104
105 105 def make_map(config):
106 106 """Create, configure and return the routes Mapper"""
107 107 rmap = JSRoutesMapper(
108 108 directory=config['pylons.paths']['controllers'],
109 109 always_scan=config['debug'])
110 110 rmap.minimization = False
111 111 rmap.explicit = False
112 112
113 113 from rhodecode.lib.utils2 import str2bool
114 114 from rhodecode.model import repo, repo_group
115 115
116 116 def check_repo(environ, match_dict):
117 117 """
118 118 check for valid repository for proper 404 handling
119 119
120 120 :param environ:
121 121 :param match_dict:
122 122 """
123 123 repo_name = match_dict.get('repo_name')
124 124
125 125 if match_dict.get('f_path'):
126 126 # fix for multiple initial slashes that causes errors
127 127 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
128 128 repo_model = repo.RepoModel()
129 129 by_name_match = repo_model.get_by_repo_name(repo_name)
130 130 # if we match quickly from database, short circuit the operation,
131 131 # and validate repo based on the type.
132 132 if by_name_match:
133 133 return True
134 134
135 135 by_id_match = repo_model.get_repo_by_id(repo_name)
136 136 if by_id_match:
137 137 repo_name = by_id_match.repo_name
138 138 match_dict['repo_name'] = repo_name
139 139 return True
140 140
141 141 return False
142 142
143 143 def check_group(environ, match_dict):
144 144 """
145 145 check for valid repository group path for proper 404 handling
146 146
147 147 :param environ:
148 148 :param match_dict:
149 149 """
150 150 repo_group_name = match_dict.get('group_name')
151 151 repo_group_model = repo_group.RepoGroupModel()
152 152 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
153 153 if by_name_match:
154 154 return True
155 155
156 156 return False
157 157
158 158 def check_user_group(environ, match_dict):
159 159 """
160 160 check for valid user group for proper 404 handling
161 161
162 162 :param environ:
163 163 :param match_dict:
164 164 """
165 165 return True
166 166
167 167 def check_int(environ, match_dict):
168 168 return match_dict.get('id').isdigit()
169 169
170 170
171 171 #==========================================================================
172 172 # CUSTOM ROUTES HERE
173 173 #==========================================================================
174 174
175 175 # ping and pylons error test
176 176 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
177 177 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
178 178
179 179 # ADMIN REPOSITORY ROUTES
180 180 with rmap.submapper(path_prefix=ADMIN_PREFIX,
181 181 controller='admin/repos') as m:
182 182 m.connect('repos', '/repos',
183 183 action='create', conditions={'method': ['POST']})
184 184 m.connect('repos', '/repos',
185 185 action='index', conditions={'method': ['GET']})
186 186 m.connect('new_repo', '/create_repository', jsroute=True,
187 187 action='create_repository', conditions={'method': ['GET']})
188 188 m.connect('delete_repo', '/repos/{repo_name}',
189 189 action='delete', conditions={'method': ['DELETE']},
190 190 requirements=URL_NAME_REQUIREMENTS)
191 191 m.connect('repo', '/repos/{repo_name}',
192 192 action='show', conditions={'method': ['GET'],
193 193 'function': check_repo},
194 194 requirements=URL_NAME_REQUIREMENTS)
195 195
196 196 # ADMIN REPOSITORY GROUPS ROUTES
197 197 with rmap.submapper(path_prefix=ADMIN_PREFIX,
198 198 controller='admin/repo_groups') as m:
199 199 m.connect('repo_groups', '/repo_groups',
200 200 action='create', conditions={'method': ['POST']})
201 201 m.connect('repo_groups', '/repo_groups',
202 202 action='index', conditions={'method': ['GET']})
203 203 m.connect('new_repo_group', '/repo_groups/new',
204 204 action='new', conditions={'method': ['GET']})
205 205 m.connect('update_repo_group', '/repo_groups/{group_name}',
206 206 action='update', conditions={'method': ['PUT'],
207 207 'function': check_group},
208 208 requirements=URL_NAME_REQUIREMENTS)
209 209
210 210 # EXTRAS REPO GROUP ROUTES
211 211 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
212 212 action='edit',
213 213 conditions={'method': ['GET'], 'function': check_group},
214 214 requirements=URL_NAME_REQUIREMENTS)
215 215 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
216 216 action='edit',
217 217 conditions={'method': ['PUT'], 'function': check_group},
218 218 requirements=URL_NAME_REQUIREMENTS)
219 219
220 220 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
221 221 action='edit_repo_group_advanced',
222 222 conditions={'method': ['GET'], 'function': check_group},
223 223 requirements=URL_NAME_REQUIREMENTS)
224 224 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
225 225 action='edit_repo_group_advanced',
226 226 conditions={'method': ['PUT'], 'function': check_group},
227 227 requirements=URL_NAME_REQUIREMENTS)
228 228
229 229 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
230 230 action='edit_repo_group_perms',
231 231 conditions={'method': ['GET'], 'function': check_group},
232 232 requirements=URL_NAME_REQUIREMENTS)
233 233 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
234 234 action='update_perms',
235 235 conditions={'method': ['PUT'], 'function': check_group},
236 236 requirements=URL_NAME_REQUIREMENTS)
237 237
238 238 m.connect('delete_repo_group', '/repo_groups/{group_name}',
239 239 action='delete', conditions={'method': ['DELETE'],
240 240 'function': check_group},
241 241 requirements=URL_NAME_REQUIREMENTS)
242 242
243 243 # ADMIN USER ROUTES
244 244 with rmap.submapper(path_prefix=ADMIN_PREFIX,
245 245 controller='admin/users') as m:
246 246 m.connect('users', '/users',
247 247 action='create', conditions={'method': ['POST']})
248 248 m.connect('new_user', '/users/new',
249 249 action='new', conditions={'method': ['GET']})
250 250 m.connect('update_user', '/users/{user_id}',
251 251 action='update', conditions={'method': ['PUT']})
252 252 m.connect('delete_user', '/users/{user_id}',
253 253 action='delete', conditions={'method': ['DELETE']})
254 254 m.connect('edit_user', '/users/{user_id}/edit',
255 255 action='edit', conditions={'method': ['GET']}, jsroute=True)
256 256 m.connect('user', '/users/{user_id}',
257 257 action='show', conditions={'method': ['GET']})
258 258 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
259 259 action='reset_password', conditions={'method': ['POST']})
260 260 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
261 261 action='create_personal_repo_group', conditions={'method': ['POST']})
262 262
263 263 # EXTRAS USER ROUTES
264 264 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
265 265 action='edit_advanced', conditions={'method': ['GET']})
266 266 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
267 267 action='update_advanced', conditions={'method': ['PUT']})
268 268
269 269 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
270 270 action='edit_global_perms', conditions={'method': ['GET']})
271 271 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
272 272 action='update_global_perms', conditions={'method': ['PUT']})
273 273
274 274 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
275 275 action='edit_perms_summary', conditions={'method': ['GET']})
276 276
277 277 # ADMIN USER GROUPS REST ROUTES
278 278 with rmap.submapper(path_prefix=ADMIN_PREFIX,
279 279 controller='admin/user_groups') as m:
280 280 m.connect('users_groups', '/user_groups',
281 281 action='create', conditions={'method': ['POST']})
282 282 m.connect('users_groups', '/user_groups',
283 283 action='index', conditions={'method': ['GET']})
284 284 m.connect('new_users_group', '/user_groups/new',
285 285 action='new', conditions={'method': ['GET']})
286 286 m.connect('update_users_group', '/user_groups/{user_group_id}',
287 287 action='update', conditions={'method': ['PUT']})
288 288 m.connect('delete_users_group', '/user_groups/{user_group_id}',
289 289 action='delete', conditions={'method': ['DELETE']})
290 290 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
291 291 action='edit', conditions={'method': ['GET']},
292 292 function=check_user_group)
293 293
294 294 # EXTRAS USER GROUP ROUTES
295 295 m.connect('edit_user_group_global_perms',
296 296 '/user_groups/{user_group_id}/edit/global_permissions',
297 297 action='edit_global_perms', conditions={'method': ['GET']})
298 298 m.connect('edit_user_group_global_perms',
299 299 '/user_groups/{user_group_id}/edit/global_permissions',
300 300 action='update_global_perms', conditions={'method': ['PUT']})
301 301 m.connect('edit_user_group_perms_summary',
302 302 '/user_groups/{user_group_id}/edit/permissions_summary',
303 303 action='edit_perms_summary', conditions={'method': ['GET']})
304 304
305 305 m.connect('edit_user_group_perms',
306 306 '/user_groups/{user_group_id}/edit/permissions',
307 307 action='edit_perms', conditions={'method': ['GET']})
308 308 m.connect('edit_user_group_perms',
309 309 '/user_groups/{user_group_id}/edit/permissions',
310 310 action='update_perms', conditions={'method': ['PUT']})
311 311
312 312 m.connect('edit_user_group_advanced',
313 313 '/user_groups/{user_group_id}/edit/advanced',
314 314 action='edit_advanced', conditions={'method': ['GET']})
315 315
316 316 m.connect('edit_user_group_advanced_sync',
317 317 '/user_groups/{user_group_id}/edit/advanced/sync',
318 318 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
319 319
320 320 m.connect('edit_user_group_members',
321 321 '/user_groups/{user_group_id}/edit/members', jsroute=True,
322 322 action='user_group_members', conditions={'method': ['GET']})
323 323
324 324 # ADMIN PERMISSIONS ROUTES
325 325 with rmap.submapper(path_prefix=ADMIN_PREFIX,
326 326 controller='admin/permissions') as m:
327 327 m.connect('admin_permissions_application', '/permissions/application',
328 328 action='permission_application_update', conditions={'method': ['POST']})
329 329 m.connect('admin_permissions_application', '/permissions/application',
330 330 action='permission_application', conditions={'method': ['GET']})
331 331
332 332 m.connect('admin_permissions_global', '/permissions/global',
333 333 action='permission_global_update', conditions={'method': ['POST']})
334 334 m.connect('admin_permissions_global', '/permissions/global',
335 335 action='permission_global', conditions={'method': ['GET']})
336 336
337 337 m.connect('admin_permissions_object', '/permissions/object',
338 338 action='permission_objects_update', conditions={'method': ['POST']})
339 339 m.connect('admin_permissions_object', '/permissions/object',
340 340 action='permission_objects', conditions={'method': ['GET']})
341 341
342 342 m.connect('admin_permissions_ips', '/permissions/ips',
343 343 action='permission_ips', conditions={'method': ['POST']})
344 344 m.connect('admin_permissions_ips', '/permissions/ips',
345 345 action='permission_ips', conditions={'method': ['GET']})
346 346
347 347 m.connect('admin_permissions_overview', '/permissions/overview',
348 348 action='permission_perms', conditions={'method': ['GET']})
349 349
350 350 # ADMIN DEFAULTS REST ROUTES
351 351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 352 controller='admin/defaults') as m:
353 353 m.connect('admin_defaults_repositories', '/defaults/repositories',
354 354 action='update_repository_defaults', conditions={'method': ['POST']})
355 355 m.connect('admin_defaults_repositories', '/defaults/repositories',
356 356 action='index', conditions={'method': ['GET']})
357 357
358 358 # ADMIN SETTINGS ROUTES
359 359 with rmap.submapper(path_prefix=ADMIN_PREFIX,
360 360 controller='admin/settings') as m:
361 361
362 362 # default
363 363 m.connect('admin_settings', '/settings',
364 364 action='settings_global_update',
365 365 conditions={'method': ['POST']})
366 366 m.connect('admin_settings', '/settings',
367 367 action='settings_global', conditions={'method': ['GET']})
368 368
369 369 m.connect('admin_settings_vcs', '/settings/vcs',
370 370 action='settings_vcs_update',
371 371 conditions={'method': ['POST']})
372 372 m.connect('admin_settings_vcs', '/settings/vcs',
373 373 action='settings_vcs',
374 374 conditions={'method': ['GET']})
375 375 m.connect('admin_settings_vcs', '/settings/vcs',
376 376 action='delete_svn_pattern',
377 377 conditions={'method': ['DELETE']})
378 378
379 379 m.connect('admin_settings_mapping', '/settings/mapping',
380 380 action='settings_mapping_update',
381 381 conditions={'method': ['POST']})
382 382 m.connect('admin_settings_mapping', '/settings/mapping',
383 383 action='settings_mapping', conditions={'method': ['GET']})
384 384
385 385 m.connect('admin_settings_global', '/settings/global',
386 386 action='settings_global_update',
387 387 conditions={'method': ['POST']})
388 388 m.connect('admin_settings_global', '/settings/global',
389 389 action='settings_global', conditions={'method': ['GET']})
390 390
391 391 m.connect('admin_settings_visual', '/settings/visual',
392 392 action='settings_visual_update',
393 393 conditions={'method': ['POST']})
394 394 m.connect('admin_settings_visual', '/settings/visual',
395 395 action='settings_visual', conditions={'method': ['GET']})
396 396
397 397 m.connect('admin_settings_issuetracker',
398 398 '/settings/issue-tracker', action='settings_issuetracker',
399 399 conditions={'method': ['GET']})
400 400 m.connect('admin_settings_issuetracker_save',
401 401 '/settings/issue-tracker/save',
402 402 action='settings_issuetracker_save',
403 403 conditions={'method': ['POST']})
404 404 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
405 405 action='settings_issuetracker_test',
406 406 conditions={'method': ['POST']})
407 407 m.connect('admin_issuetracker_delete',
408 408 '/settings/issue-tracker/delete',
409 409 action='settings_issuetracker_delete',
410 410 conditions={'method': ['DELETE']})
411 411
412 412 m.connect('admin_settings_email', '/settings/email',
413 413 action='settings_email_update',
414 414 conditions={'method': ['POST']})
415 415 m.connect('admin_settings_email', '/settings/email',
416 416 action='settings_email', conditions={'method': ['GET']})
417 417
418 418 m.connect('admin_settings_hooks', '/settings/hooks',
419 419 action='settings_hooks_update',
420 420 conditions={'method': ['POST', 'DELETE']})
421 421 m.connect('admin_settings_hooks', '/settings/hooks',
422 422 action='settings_hooks', conditions={'method': ['GET']})
423 423
424 424 m.connect('admin_settings_search', '/settings/search',
425 425 action='settings_search', conditions={'method': ['GET']})
426 426
427 427 m.connect('admin_settings_supervisor', '/settings/supervisor',
428 428 action='settings_supervisor', conditions={'method': ['GET']})
429 429 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
430 430 action='settings_supervisor_log', conditions={'method': ['GET']})
431 431
432 432 m.connect('admin_settings_labs', '/settings/labs',
433 433 action='settings_labs_update',
434 434 conditions={'method': ['POST']})
435 435 m.connect('admin_settings_labs', '/settings/labs',
436 436 action='settings_labs', conditions={'method': ['GET']})
437 437
438 438 # ADMIN MY ACCOUNT
439 439 with rmap.submapper(path_prefix=ADMIN_PREFIX,
440 440 controller='admin/my_account') as m:
441 441
442 442 # NOTE(marcink): this needs to be kept for password force flag to be
443 443 # handled in pylons controllers, remove after full migration to pyramid
444 444 m.connect('my_account_password', '/my_account/password',
445 445 action='my_account_password', conditions={'method': ['GET']})
446 446
447 # USER JOURNAL
448 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
449 controller='journal', action='index')
450 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
451 controller='journal', action='journal_rss')
452 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
453 controller='journal', action='journal_atom')
454
455 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
456 controller='journal', action='public_journal')
457
458 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
459 controller='journal', action='public_journal_rss')
460
461 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
462 controller='journal', action='public_journal_rss')
463
464 rmap.connect('public_journal_atom',
465 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
466 action='public_journal_atom')
467
468 rmap.connect('public_journal_atom_old',
469 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
470 action='public_journal_atom')
471
472 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
473 controller='journal', action='toggle_following', jsroute=True,
474 conditions={'method': ['POST']})
475
476 447 #==========================================================================
477 448 # REPOSITORY ROUTES
478 449 #==========================================================================
479 450
480 451 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
481 452 controller='admin/repos', action='repo_creating',
482 453 requirements=URL_NAME_REQUIREMENTS)
483 454 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
484 455 controller='admin/repos', action='repo_check',
485 456 requirements=URL_NAME_REQUIREMENTS)
486 457
487 458 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
488 459 controller='changeset', revision='tip',
489 460 conditions={'function': check_repo},
490 461 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
491 462 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
492 463 controller='changeset', revision='tip', action='changeset_children',
493 464 conditions={'function': check_repo},
494 465 requirements=URL_NAME_REQUIREMENTS)
495 466 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
496 467 controller='changeset', revision='tip', action='changeset_parents',
497 468 conditions={'function': check_repo},
498 469 requirements=URL_NAME_REQUIREMENTS)
499 470
500 471 # repo edit options
501 472 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
502 473 controller='admin/repos', action='edit_fields',
503 474 conditions={'method': ['GET'], 'function': check_repo},
504 475 requirements=URL_NAME_REQUIREMENTS)
505 476 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
506 477 controller='admin/repos', action='create_repo_field',
507 478 conditions={'method': ['PUT'], 'function': check_repo},
508 479 requirements=URL_NAME_REQUIREMENTS)
509 480 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
510 481 controller='admin/repos', action='delete_repo_field',
511 482 conditions={'method': ['DELETE'], 'function': check_repo},
512 483 requirements=URL_NAME_REQUIREMENTS)
513 484
514 485 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
515 486 controller='admin/repos', action='toggle_locking',
516 487 conditions={'method': ['GET'], 'function': check_repo},
517 488 requirements=URL_NAME_REQUIREMENTS)
518 489
519 490 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
520 491 controller='admin/repos', action='edit_remote_form',
521 492 conditions={'method': ['GET'], 'function': check_repo},
522 493 requirements=URL_NAME_REQUIREMENTS)
523 494 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
524 495 controller='admin/repos', action='edit_remote',
525 496 conditions={'method': ['PUT'], 'function': check_repo},
526 497 requirements=URL_NAME_REQUIREMENTS)
527 498
528 499 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
529 500 controller='admin/repos', action='edit_statistics_form',
530 501 conditions={'method': ['GET'], 'function': check_repo},
531 502 requirements=URL_NAME_REQUIREMENTS)
532 503 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
533 504 controller='admin/repos', action='edit_statistics',
534 505 conditions={'method': ['PUT'], 'function': check_repo},
535 506 requirements=URL_NAME_REQUIREMENTS)
536 507 rmap.connect('repo_settings_issuetracker',
537 508 '/{repo_name}/settings/issue-tracker',
538 509 controller='admin/repos', action='repo_issuetracker',
539 510 conditions={'method': ['GET'], 'function': check_repo},
540 511 requirements=URL_NAME_REQUIREMENTS)
541 512 rmap.connect('repo_issuetracker_test',
542 513 '/{repo_name}/settings/issue-tracker/test',
543 514 controller='admin/repos', action='repo_issuetracker_test',
544 515 conditions={'method': ['POST'], 'function': check_repo},
545 516 requirements=URL_NAME_REQUIREMENTS)
546 517 rmap.connect('repo_issuetracker_delete',
547 518 '/{repo_name}/settings/issue-tracker/delete',
548 519 controller='admin/repos', action='repo_issuetracker_delete',
549 520 conditions={'method': ['DELETE'], 'function': check_repo},
550 521 requirements=URL_NAME_REQUIREMENTS)
551 522 rmap.connect('repo_issuetracker_save',
552 523 '/{repo_name}/settings/issue-tracker/save',
553 524 controller='admin/repos', action='repo_issuetracker_save',
554 525 conditions={'method': ['POST'], 'function': check_repo},
555 526 requirements=URL_NAME_REQUIREMENTS)
556 527 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
557 528 controller='admin/repos', action='repo_settings_vcs_update',
558 529 conditions={'method': ['POST'], 'function': check_repo},
559 530 requirements=URL_NAME_REQUIREMENTS)
560 531 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
561 532 controller='admin/repos', action='repo_settings_vcs',
562 533 conditions={'method': ['GET'], 'function': check_repo},
563 534 requirements=URL_NAME_REQUIREMENTS)
564 535 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
565 536 controller='admin/repos', action='repo_delete_svn_pattern',
566 537 conditions={'method': ['DELETE'], 'function': check_repo},
567 538 requirements=URL_NAME_REQUIREMENTS)
568 539 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
569 540 controller='admin/repos', action='repo_settings_pullrequest',
570 541 conditions={'method': ['GET', 'POST'], 'function': check_repo},
571 542 requirements=URL_NAME_REQUIREMENTS)
572 543
573 544 # still working url for backward compat.
574 545 rmap.connect('raw_changeset_home_depraced',
575 546 '/{repo_name}/raw-changeset/{revision}',
576 547 controller='changeset', action='changeset_raw',
577 548 revision='tip', conditions={'function': check_repo},
578 549 requirements=URL_NAME_REQUIREMENTS)
579 550
580 551 # new URLs
581 552 rmap.connect('changeset_raw_home',
582 553 '/{repo_name}/changeset-diff/{revision}',
583 554 controller='changeset', action='changeset_raw',
584 555 revision='tip', conditions={'function': check_repo},
585 556 requirements=URL_NAME_REQUIREMENTS)
586 557
587 558 rmap.connect('changeset_patch_home',
588 559 '/{repo_name}/changeset-patch/{revision}',
589 560 controller='changeset', action='changeset_patch',
590 561 revision='tip', conditions={'function': check_repo},
591 562 requirements=URL_NAME_REQUIREMENTS)
592 563
593 564 rmap.connect('changeset_download_home',
594 565 '/{repo_name}/changeset-download/{revision}',
595 566 controller='changeset', action='changeset_download',
596 567 revision='tip', conditions={'function': check_repo},
597 568 requirements=URL_NAME_REQUIREMENTS)
598 569
599 570 rmap.connect('changeset_comment',
600 571 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
601 572 controller='changeset', revision='tip', action='comment',
602 573 conditions={'function': check_repo},
603 574 requirements=URL_NAME_REQUIREMENTS)
604 575
605 576 rmap.connect('changeset_comment_preview',
606 577 '/{repo_name}/changeset/comment/preview', jsroute=True,
607 578 controller='changeset', action='preview_comment',
608 579 conditions={'function': check_repo, 'method': ['POST']},
609 580 requirements=URL_NAME_REQUIREMENTS)
610 581
611 582 rmap.connect('changeset_comment_delete',
612 583 '/{repo_name}/changeset/comment/{comment_id}/delete',
613 584 controller='changeset', action='delete_comment',
614 585 conditions={'function': check_repo, 'method': ['DELETE']},
615 586 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
616 587
617 588 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
618 589 controller='changeset', action='changeset_info',
619 590 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
620 591
621 592 rmap.connect('compare_home',
622 593 '/{repo_name}/compare',
623 594 controller='compare', action='index',
624 595 conditions={'function': check_repo},
625 596 requirements=URL_NAME_REQUIREMENTS)
626 597
627 598 rmap.connect('compare_url',
628 599 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
629 600 controller='compare', action='compare',
630 601 conditions={'function': check_repo},
631 602 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
632 603
633 604 rmap.connect('pullrequest_home',
634 605 '/{repo_name}/pull-request/new', controller='pullrequests',
635 606 action='index', conditions={'function': check_repo,
636 607 'method': ['GET']},
637 608 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
638 609
639 610 rmap.connect('pullrequest',
640 611 '/{repo_name}/pull-request/new', controller='pullrequests',
641 612 action='create', conditions={'function': check_repo,
642 613 'method': ['POST']},
643 614 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
644 615
645 616 rmap.connect('pullrequest_repo_refs',
646 617 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
647 618 controller='pullrequests',
648 619 action='get_repo_refs',
649 620 conditions={'function': check_repo, 'method': ['GET']},
650 621 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
651 622
652 623 rmap.connect('pullrequest_repo_destinations',
653 624 '/{repo_name}/pull-request/repo-destinations',
654 625 controller='pullrequests',
655 626 action='get_repo_destinations',
656 627 conditions={'function': check_repo, 'method': ['GET']},
657 628 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
658 629
659 630 rmap.connect('pullrequest_show',
660 631 '/{repo_name}/pull-request/{pull_request_id}',
661 632 controller='pullrequests',
662 633 action='show', conditions={'function': check_repo,
663 634 'method': ['GET']},
664 635 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
665 636
666 637 rmap.connect('pullrequest_update',
667 638 '/{repo_name}/pull-request/{pull_request_id}',
668 639 controller='pullrequests',
669 640 action='update', conditions={'function': check_repo,
670 641 'method': ['PUT']},
671 642 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
672 643
673 644 rmap.connect('pullrequest_merge',
674 645 '/{repo_name}/pull-request/{pull_request_id}',
675 646 controller='pullrequests',
676 647 action='merge', conditions={'function': check_repo,
677 648 'method': ['POST']},
678 649 requirements=URL_NAME_REQUIREMENTS)
679 650
680 651 rmap.connect('pullrequest_delete',
681 652 '/{repo_name}/pull-request/{pull_request_id}',
682 653 controller='pullrequests',
683 654 action='delete', conditions={'function': check_repo,
684 655 'method': ['DELETE']},
685 656 requirements=URL_NAME_REQUIREMENTS)
686 657
687 658 rmap.connect('pullrequest_comment',
688 659 '/{repo_name}/pull-request-comment/{pull_request_id}',
689 660 controller='pullrequests',
690 661 action='comment', conditions={'function': check_repo,
691 662 'method': ['POST']},
692 663 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
693 664
694 665 rmap.connect('pullrequest_comment_delete',
695 666 '/{repo_name}/pull-request-comment/{comment_id}/delete',
696 667 controller='pullrequests', action='delete_comment',
697 668 conditions={'function': check_repo, 'method': ['DELETE']},
698 669 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
699 670
700 671 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
701 672 controller='forks', action='fork_create',
702 673 conditions={'function': check_repo, 'method': ['POST']},
703 674 requirements=URL_NAME_REQUIREMENTS)
704 675
705 676 rmap.connect('repo_fork_home', '/{repo_name}/fork',
706 677 controller='forks', action='fork',
707 678 conditions={'function': check_repo},
708 679 requirements=URL_NAME_REQUIREMENTS)
709 680
710 681 rmap.connect('repo_forks_home', '/{repo_name}/forks',
711 682 controller='forks', action='forks',
712 683 conditions={'function': check_repo},
713 684 requirements=URL_NAME_REQUIREMENTS)
714 685
715 686 return rmap
@@ -1,198 +1,206 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
19 18 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
20 19 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
21 20 pyroutes.register('changeset_comment_preview', '/%(repo_name)s/changeset/comment/preview', ['repo_name']);
22 21 pyroutes.register('changeset_comment_delete', '/%(repo_name)s/changeset/comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
23 22 pyroutes.register('changeset_info', '/%(repo_name)s/changeset_info/%(revision)s', ['repo_name', 'revision']);
24 23 pyroutes.register('compare_url', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
25 24 pyroutes.register('pullrequest_home', '/%(repo_name)s/pull-request/new', ['repo_name']);
26 25 pyroutes.register('pullrequest', '/%(repo_name)s/pull-request/new', ['repo_name']);
27 26 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
28 27 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
29 28 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
30 29 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
31 30 pyroutes.register('pullrequest_comment', '/%(repo_name)s/pull-request-comment/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
32 31 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request-comment/%(comment_id)s/delete', ['repo_name', 'comment_id']);
33 32 pyroutes.register('favicon', '/favicon.ico', []);
34 33 pyroutes.register('robots', '/robots.txt', []);
35 34 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
36 35 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
37 36 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
38 37 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
39 38 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
40 39 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
41 40 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
42 41 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
43 42 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
44 43 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
45 44 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
46 45 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
47 46 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
48 47 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
49 48 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
50 49 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
51 50 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
52 51 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
53 52 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
54 53 pyroutes.register('admin_home', '/_admin', []);
55 54 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
56 55 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
57 56 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
58 57 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
59 58 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
60 59 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
61 60 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
62 61 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
63 62 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
64 63 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
65 64 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
66 65 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
67 66 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
68 67 pyroutes.register('users', '/_admin/users', []);
69 68 pyroutes.register('users_data', '/_admin/users_data', []);
70 69 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
71 70 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
72 71 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
73 72 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
74 73 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
75 74 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
76 75 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
77 76 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
78 77 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
79 78 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
80 79 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
81 80 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
82 81 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
83 82 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
84 83 pyroutes.register('channelstream_proxy', '/_channelstream', []);
85 84 pyroutes.register('login', '/_admin/login', []);
86 85 pyroutes.register('logout', '/_admin/logout', []);
87 86 pyroutes.register('register', '/_admin/register', []);
88 87 pyroutes.register('reset_password', '/_admin/password_reset', []);
89 88 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
90 89 pyroutes.register('home', '/', []);
91 90 pyroutes.register('user_autocomplete_data', '/_users', []);
92 91 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
93 92 pyroutes.register('repo_list_data', '/_repos', []);
94 93 pyroutes.register('goto_switcher_data', '/_goto_data', []);
94 pyroutes.register('journal', '/_admin/journal', []);
95 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
96 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
97 pyroutes.register('journal_public', '/_admin/public_journal', []);
98 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
99 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
100 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
101 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
102 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
95 103 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
96 104 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
97 105 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
98 106 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
99 107 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
100 108 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
101 109 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
102 110 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
103 111 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
104 112 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
105 113 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
106 114 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
107 115 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
108 116 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
109 117 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
110 118 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
111 119 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
112 120 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
113 121 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
114 122 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
115 123 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
116 124 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
117 125 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
118 126 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
119 127 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
120 128 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
121 129 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
122 130 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
123 131 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
124 132 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
125 133 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
126 134 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
127 135 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
128 136 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
129 137 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
130 138 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
131 139 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
132 140 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
133 141 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
134 142 pyroutes.register('changeset_children', '/%(repo_name)s/changeset_children/%(revision)s', ['repo_name', 'revision']);
135 143 pyroutes.register('changeset_parents', '/%(repo_name)s/changeset_parents/%(revision)s', ['repo_name', 'revision']);
136 144 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
137 145 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
138 146 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
139 147 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
140 148 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
141 149 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
142 150 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
143 151 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
144 152 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
145 153 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
146 154 pyroutes.register('repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
147 155 pyroutes.register('repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
148 156 pyroutes.register('strip', '/%(repo_name)s/settings/strip', ['repo_name']);
149 157 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
150 158 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
151 159 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
152 160 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
153 161 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
154 162 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
155 163 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
156 164 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
157 165 pyroutes.register('search', '/_admin/search', []);
158 166 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
159 167 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
160 168 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
161 169 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
162 170 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
163 171 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
164 172 pyroutes.register('my_account_password_update', '/_admin/my_account/password', []);
165 173 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
166 174 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
167 175 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
168 176 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
169 177 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
170 178 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
171 179 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
172 180 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
173 181 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
174 182 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
175 183 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
176 184 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
177 185 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
178 186 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
179 187 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
180 188 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
181 189 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
182 190 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
183 191 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
184 192 pyroutes.register('gists_show', '/_admin/gists', []);
185 193 pyroutes.register('gists_new', '/_admin/gists/new', []);
186 194 pyroutes.register('gists_create', '/_admin/gists/create', []);
187 195 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
188 196 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
189 197 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
190 198 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
191 199 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
192 200 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
193 201 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
194 202 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
195 203 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
196 204 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
197 205 pyroutes.register('apiv2', '/_admin/api', []);
198 206 }
@@ -1,600 +1,600 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${h.tooltip(title)}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink dropdown">
247 247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 248 </a>
249 249 <ul class="submenu">
250 250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 252 %endif
253 253 %if c.rhodecode_db_repo.fork:
254 254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
255 255 ${_('Compare fork')}</a></li>
256 256 %endif
257 257
258 258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
259 259
260 260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
261 261 %if c.rhodecode_db_repo.locked[0]:
262 262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
263 263 %else:
264 264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
265 265 %endif
266 266 %endif
267 267 %if c.rhodecode_user.username != h.DEFAULT_USER:
268 268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
269 269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
270 270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
271 271 %endif
272 272 %endif
273 273 </ul>
274 274 </li>
275 275 </ul>
276 276 </div>
277 277 <div class="clear"></div>
278 278 </div>
279 279 <!--- END CONTEXT BAR -->
280 280
281 281 </%def>
282 282
283 283 <%def name="usermenu(active=False)">
284 284 ## USER MENU
285 285 <li id="quick_login_li" class="${'active' if active else ''}">
286 286 <a id="quick_login_link" class="menulink childs">
287 287 ${gravatar(c.rhodecode_user.email, 20)}
288 288 <span class="user">
289 289 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
291 291 %else:
292 292 <span>${_('Sign in')}</span>
293 293 %endif
294 294 </span>
295 295 </a>
296 296
297 297 <div class="user-menu submenu">
298 298 <div id="quick_login">
299 299 %if c.rhodecode_user.username == h.DEFAULT_USER:
300 300 <h4>${_('Sign in to your account')}</h4>
301 301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
302 302 <div class="form form-vertical">
303 303 <div class="fields">
304 304 <div class="field">
305 305 <div class="label">
306 306 <label for="username">${_('Username')}:</label>
307 307 </div>
308 308 <div class="input">
309 309 ${h.text('username',class_='focus',tabindex=1)}
310 310 </div>
311 311
312 312 </div>
313 313 <div class="field">
314 314 <div class="label">
315 315 <label for="password">${_('Password')}:</label>
316 316 %if h.HasPermissionAny('hg.password_reset.enabled')():
317 317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
318 318 %endif
319 319 </div>
320 320 <div class="input">
321 321 ${h.password('password',class_='focus',tabindex=2)}
322 322 </div>
323 323 </div>
324 324 <div class="buttons">
325 325 <div class="register">
326 326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
327 327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
328 328 %endif
329 329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
330 330 </div>
331 331 <div class="submit">
332 332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
333 333 </div>
334 334 </div>
335 335 </div>
336 336 </div>
337 337 ${h.end_form()}
338 338 %else:
339 339 <div class="">
340 340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
341 341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
342 342 <div class="email">${c.rhodecode_user.email}</div>
343 343 </div>
344 344 <div class="">
345 345 <ol class="links">
346 346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
347 347 % if c.rhodecode_user.personal_repo_group:
348 348 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
349 349 % endif
350 350 <li class="logout">
351 351 ${h.secure_form(h.route_path('logout'), request=request)}
352 352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
353 353 ${h.end_form()}
354 354 </li>
355 355 </ol>
356 356 </div>
357 357 %endif
358 358 </div>
359 359 </div>
360 360 %if c.rhodecode_user.username != h.DEFAULT_USER:
361 361 <div class="pill_container">
362 362 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
363 363 </div>
364 364 % endif
365 365 </li>
366 366 </%def>
367 367
368 368 <%def name="menu_items(active=None)">
369 369 <%
370 370 def is_active(selected):
371 371 if selected == active:
372 372 return "active"
373 373 return ""
374 374 %>
375 375 <ul id="quick" class="main_nav navigation horizontal-list">
376 376 <!-- repo switcher -->
377 377 <li class="${is_active('repositories')} repo_switcher_li has_select2">
378 378 <input id="repo_switcher" name="repo_switcher" type="hidden">
379 379 </li>
380 380
381 381 ## ROOT MENU
382 382 %if c.rhodecode_user.username != h.DEFAULT_USER:
383 383 <li class="${is_active('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
384 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
385 385 <div class="menulabel">${_('Journal')}</div>
386 386 </a>
387 387 </li>
388 388 %else:
389 389 <li class="${is_active('journal')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
390 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
391 391 <div class="menulabel">${_('Public journal')}</div>
392 392 </a>
393 393 </li>
394 394 %endif
395 395 <li class="${is_active('gists')}">
396 396 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
397 397 <div class="menulabel">${_('Gists')}</div>
398 398 </a>
399 399 </li>
400 400 <li class="${is_active('search')}">
401 401 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
402 402 <div class="menulabel">${_('Search')}</div>
403 403 </a>
404 404 </li>
405 405 % if h.HasPermissionAll('hg.admin')('access admin main page'):
406 406 <li class="${is_active('admin')}">
407 407 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
408 408 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
409 409 </a>
410 410 ${admin_menu()}
411 411 </li>
412 412 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
413 413 <li class="${is_active('admin')}">
414 414 <a class="menulink childs" title="${_('Delegated Admin settings')}">
415 415 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
416 416 </a>
417 417 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
418 418 c.rhodecode_user.repository_groups_admin,
419 419 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
420 420 </li>
421 421 % endif
422 422 % if c.debug_style:
423 423 <li class="${is_active('debug_style')}">
424 424 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
425 425 <div class="menulabel">${_('Style')}</div>
426 426 </a>
427 427 </li>
428 428 % endif
429 429 ## render extra user menu
430 430 ${usermenu(active=(active=='my_account'))}
431 431 </ul>
432 432
433 433 <script type="text/javascript">
434 434 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
435 435
436 436 /*format the look of items in the list*/
437 437 var format = function(state, escapeMarkup){
438 438 if (!state.id){
439 439 return state.text; // optgroup
440 440 }
441 441 var obj_dict = state.obj;
442 442 var tmpl = '';
443 443
444 444 if(obj_dict && state.type == 'repo'){
445 445 if(obj_dict['repo_type'] === 'hg'){
446 446 tmpl += '<i class="icon-hg"></i> ';
447 447 }
448 448 else if(obj_dict['repo_type'] === 'git'){
449 449 tmpl += '<i class="icon-git"></i> ';
450 450 }
451 451 else if(obj_dict['repo_type'] === 'svn'){
452 452 tmpl += '<i class="icon-svn"></i> ';
453 453 }
454 454 if(obj_dict['private']){
455 455 tmpl += '<i class="icon-lock" ></i> ';
456 456 }
457 457 else if(visual_show_public_icon){
458 458 tmpl += '<i class="icon-unlock-alt"></i> ';
459 459 }
460 460 }
461 461 if(obj_dict && state.type == 'commit') {
462 462 tmpl += '<i class="icon-tag"></i>';
463 463 }
464 464 if(obj_dict && state.type == 'group'){
465 465 tmpl += '<i class="icon-folder-close"></i> ';
466 466 }
467 467 tmpl += escapeMarkup(state.text);
468 468 return tmpl;
469 469 };
470 470
471 471 var formatResult = function(result, container, query, escapeMarkup) {
472 472 return format(result, escapeMarkup);
473 473 };
474 474
475 475 var formatSelection = function(data, container, escapeMarkup) {
476 476 return format(data, escapeMarkup);
477 477 };
478 478
479 479 $("#repo_switcher").select2({
480 480 cachedDataSource: {},
481 481 minimumInputLength: 2,
482 482 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
483 483 dropdownAutoWidth: true,
484 484 formatResult: formatResult,
485 485 formatSelection: formatSelection,
486 486 containerCssClass: "repo-switcher",
487 487 dropdownCssClass: "repo-switcher-dropdown",
488 488 escapeMarkup: function(m){
489 489 // don't escape our custom placeholder
490 490 if(m.substr(0,23) == '<div class="menulabel">'){
491 491 return m;
492 492 }
493 493
494 494 return Select2.util.escapeMarkup(m);
495 495 },
496 496 query: $.debounce(250, function(query){
497 497 self = this;
498 498 var cacheKey = query.term;
499 499 var cachedData = self.cachedDataSource[cacheKey];
500 500
501 501 if (cachedData) {
502 502 query.callback({results: cachedData.results});
503 503 } else {
504 504 $.ajax({
505 505 url: pyroutes.url('goto_switcher_data'),
506 506 data: {'query': query.term},
507 507 dataType: 'json',
508 508 type: 'GET',
509 509 success: function(data) {
510 510 self.cachedDataSource[cacheKey] = data;
511 511 query.callback({results: data.results});
512 512 },
513 513 error: function(data, textStatus, errorThrown) {
514 514 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
515 515 }
516 516 })
517 517 }
518 518 })
519 519 });
520 520
521 521 $("#repo_switcher").on('select2-selecting', function(e){
522 522 e.preventDefault();
523 523 window.location = e.choice.url;
524 524 });
525 525
526 526 </script>
527 527 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
528 528 </%def>
529 529
530 530 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
531 531 <div class="modal-dialog">
532 532 <div class="modal-content">
533 533 <div class="modal-header">
534 534 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
535 535 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
536 536 </div>
537 537 <div class="modal-body">
538 538 <div class="block-left">
539 539 <table class="keyboard-mappings">
540 540 <tbody>
541 541 <tr>
542 542 <th></th>
543 543 <th>${_('Site-wide shortcuts')}</th>
544 544 </tr>
545 545 <%
546 546 elems = [
547 547 ('/', 'Open quick search box'),
548 548 ('g h', 'Goto home page'),
549 549 ('g g', 'Goto my private gists page'),
550 550 ('g G', 'Goto my public gists page'),
551 551 ('n r', 'New repository page'),
552 552 ('n g', 'New gist page'),
553 553 ]
554 554 %>
555 555 %for key, desc in elems:
556 556 <tr>
557 557 <td class="keys">
558 558 <span class="key tag">${key}</span>
559 559 </td>
560 560 <td>${desc}</td>
561 561 </tr>
562 562 %endfor
563 563 </tbody>
564 564 </table>
565 565 </div>
566 566 <div class="block-left">
567 567 <table class="keyboard-mappings">
568 568 <tbody>
569 569 <tr>
570 570 <th></th>
571 571 <th>${_('Repositories')}</th>
572 572 </tr>
573 573 <%
574 574 elems = [
575 575 ('g s', 'Goto summary page'),
576 576 ('g c', 'Goto changelog page'),
577 577 ('g f', 'Goto files page'),
578 578 ('g F', 'Goto files page with file search activated'),
579 579 ('g p', 'Goto pull requests page'),
580 580 ('g o', 'Goto repository settings'),
581 581 ('g O', 'Goto repository permissions settings'),
582 582 ]
583 583 %>
584 584 %for key, desc in elems:
585 585 <tr>
586 586 <td class="keys">
587 587 <span class="key tag">${key}</span>
588 588 </td>
589 589 <td>${desc}</td>
590 590 </tr>
591 591 %endfor
592 592 </tbody>
593 593 </table>
594 594 </div>
595 595 </div>
596 596 <div class="modal-footer">
597 597 </div>
598 598 </div><!-- /.modal-content -->
599 599 </div><!-- /.modal-dialog -->
600 600 </div><!-- /.modal -->
@@ -1,54 +1,56 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3 <%def name="title()">
4 4 ${_('Journal')}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9
9 10 <%def name="breadcrumbs()">
10 11 <h1 class="block-left">
11 12 ${h.form(None, id_="filter_form", method="get")}
12 13 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term}" placeholder="${_('quick filter...')}"/>
13 14 <input type='submit' value="${_('Filter')}" class="btn" />
14 ${_('Journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
15 ${_('Journal')} - ${_ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
15 16 ${h.end_form()}
16 17 </h1>
17 18 <p class="tooltip filterexample" title="${h.tooltip(h.journal_filter_help(c.pyramid_request))}">${_('Example Queries')}</p>
18 19 </%def>
19 20 <%def name="menu_bar_nav()">
20 21 ${self.menu_items(active='journal')}
21 22 </%def>
22 23 <%def name="head_extra()">
23 <link href="${h.url('journal_atom', auth_token=c.rhodecode_user.feed_token)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
24 <link href="${h.url('journal_rss', auth_token=c.rhodecode_user.feed_token)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
24 <link href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
25 <link href="${h.route_path('journal_rss', _query=dict(auth_token=c.rhodecode_user.feed_token))}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
25 26 </%def>
27
26 28 <%def name="main()">
27 29
28 30 <div class="box">
29 31 <!-- box / title -->
30 32 <div class="title journal">
31 33 ${self.breadcrumbs()}
32 34 <ul class="links icon-only-links block-right">
33 35 <li>
34 <span><a id="refresh" href="${h.url('journal')}"><i class="icon-refresh"></i></a></span>
36 <span><a id="refresh" href="${h.route_path('journal')}"><i class="icon-refresh"></i></a></span>
35 37 </li>
36 38 <li>
37 <span><a href="${h.url('journal_atom', auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a></span>
39 <span><a href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a></span>
38 40 </li>
39 41 </ul>
40 42 </div>
41 <div id="journal">${c.journal_data}</div>
43 <div id="journal">${c.journal_data|n}</div>
42 44 </div>
43 45
44 46 <script type="text/javascript">
45 47
46 48 $('#j_filter').autoGrowInput();
47 49 $(document).on('pjax:success',function(){
48 50 show_more_event();
49 51 });
50 52 $(document).pjax('#refresh', '#journal',
51 {url: "${h.url.current(filter=c.search_term)}", push: false});
53 {url: "${request.current_route_path(_query=dict(filter=c.search_term))}", push: false});
52 54
53 55 </script>
54 56 </%def>
@@ -1,37 +1,43 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3 <%def name="title()">
4 4 ${_('Public Journal')}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9
9 10 <%def name="breadcrumbs()">
10 ${h.branding(c.rhodecode_name)}
11 <h1 class="block-left">
12 ${_('Public Journal')} - ${_ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
13 </h1>
11 14 </%def>
15
12 16 <%def name="menu_bar_nav()">
13 17 ${self.menu_items(active='journal')}
14 18 </%def>
19
15 20 <%def name="head_extra()">
16 <link href="${h.url('public_journal_atom')}" rel="alternate" title="${_('ATOM public journal feed')}" type="application/atom+xml" />
17 <link href="${h.url('public_journal_rss')}" rel="alternate" title="${_('RSS public journal feed')}" type="application/rss+xml" />
21 <link href="${h.route_path('journal_public_atom')}" rel="alternate" title="${_('ATOM public journal feed')}" type="application/atom+xml" />
22 <link href="${h.route_path('journal_public_rss')}" rel="alternate" title="${_('RSS public journal feed')}" type="application/rss+xml" />
18 23 </%def>
24
19 25 <%def name="main()">
20 26
21 27 <div class="box">
22 28 <!-- box / title -->
23 <div class="title">
24 <h5>${_('Public Journal')}</h5>
25 <ul class="links">
29 <div class="title journal">
30 ${self.breadcrumbs()}
31
32 <ul class="links icon-only-links block-right">
26 33 <li>
27 34 <span>
28 <a href="${h.url('public_journal_atom')}"> <i class="icon-rss-sign" ></i></a>
35 <a href="${h.route_path('journal_public_atom')}"> <i class="icon-rss-sign" ></i></a>
29 36 </span>
30 37 </li>
31 </ul>
38 </ul>
32 39 </div>
33
34 <div id="journal">${c.journal_data}</div>
40 <div id="journal">${c.journal_data|n}</div>
35 41 </div>
36 42
37 43 </%def>
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now