##// END OF EJS Templates
integrations: refactor/cleanup + features, fixes #4181...
dan -
r731:7a6d3636 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -0,0 +1,35 b''
1 import logging
2 import datetime
3
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
9
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12 from rhodecode.lib.utils2 import str2bool
13
14 from rhodecode.model.meta import Base
15 from rhodecode.model import meta
16 from rhodecode.lib.dbmigrate.versions import _reset_base, notify
17
18 log = logging.getLogger(__name__)
19
20
21 def upgrade(migrate_engine):
22 """
23 Upgrade operations go here.
24 Don't create your own engine; bind migrate_engine to your metadata
25 """
26 _reset_base(migrate_engine)
27 from rhodecode.lib.dbmigrate.schema import db_4_4_0_1
28
29 tbl = db_4_4_0_1.Integration.__table__
30 child_repos_only = db_4_4_0_1.Integration.child_repos_only
31 child_repos_only.create(table=tbl)
32
33 def downgrade(migrate_engine):
34 meta = MetaData()
35 meta.bind = migrate_engine
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,62 +1,62 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pylons
43 # link to config for pylons
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 56 # defines current db version for migrations
54 __dbversion__ = 57 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'http://rhodecode.com'
58 __url__ = 'http://rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
@@ -1,1159 +1,1160 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Routes configuration
22 Routes configuration
23
23
24 The more specific and detailed routes should be defined first so they
24 The more specific and detailed routes should be defined first so they
25 may take precedent over the more generic routes. For more information
25 may take precedent over the more generic routes. For more information
26 refer to the routes manual at http://routes.groovie.org/docs/
26 refer to the routes manual at http://routes.groovie.org/docs/
27
27
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 and _route_name variable which uses some of stored naming here to do redirects.
29 and _route_name variable which uses some of stored naming here to do redirects.
30 """
30 """
31 import os
31 import os
32 import re
32 import re
33 from routes import Mapper
33 from routes import Mapper
34
34
35 from rhodecode.config import routing_links
35 from rhodecode.config import routing_links
36
36
37 # prefix for non repository related links needs to be prefixed with `/`
37 # prefix for non repository related links needs to be prefixed with `/`
38 ADMIN_PREFIX = '/_admin'
38 ADMIN_PREFIX = '/_admin'
39 STATIC_FILE_PREFIX = '/_static'
39 STATIC_FILE_PREFIX = '/_static'
40
40
41 # Default requirements for URL parts
41 # Default requirements for URL parts
42 URL_NAME_REQUIREMENTS = {
42 URL_NAME_REQUIREMENTS = {
43 # group name can have a slash in them, but they must not end with a slash
43 # group name can have a slash in them, but they must not end with a slash
44 'group_name': r'.*?[^/]',
44 'group_name': r'.*?[^/]',
45 'repo_group_name': r'.*?[^/]',
45 # repo names can have a slash in them, but they must not end with a slash
46 # repo names can have a slash in them, but they must not end with a slash
46 'repo_name': r'.*?[^/]',
47 'repo_name': r'.*?[^/]',
47 # file path eats up everything at the end
48 # file path eats up everything at the end
48 'f_path': r'.*',
49 'f_path': r'.*',
49 # reference types
50 # reference types
50 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
51 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
52 }
53 }
53
54
54
55
55 def add_route_requirements(route_path, requirements):
56 def add_route_requirements(route_path, requirements):
56 """
57 """
57 Adds regex requirements to pyramid routes using a mapping dict
58 Adds regex requirements to pyramid routes using a mapping dict
58
59
59 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
60 '/{action}/{id:\d+}'
61 '/{action}/{id:\d+}'
61
62
62 """
63 """
63 for key, regex in requirements.items():
64 for key, regex in requirements.items():
64 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
65 return route_path
66 return route_path
66
67
67
68
68 class JSRoutesMapper(Mapper):
69 class JSRoutesMapper(Mapper):
69 """
70 """
70 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
71 """
72 """
72 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
73 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
74 def __init__(self, *args, **kw):
75 def __init__(self, *args, **kw):
75 super(JSRoutesMapper, self).__init__(*args, **kw)
76 super(JSRoutesMapper, self).__init__(*args, **kw)
76 self._jsroutes = []
77 self._jsroutes = []
77
78
78 def connect(self, *args, **kw):
79 def connect(self, *args, **kw):
79 """
80 """
80 Wrapper for connect to take an extra argument jsroute=True
81 Wrapper for connect to take an extra argument jsroute=True
81
82
82 :param jsroute: boolean, if True will add the route to the pyroutes list
83 :param jsroute: boolean, if True will add the route to the pyroutes list
83 """
84 """
84 if kw.pop('jsroute', False):
85 if kw.pop('jsroute', False):
85 if not self._named_route_regex.match(args[0]):
86 if not self._named_route_regex.match(args[0]):
86 raise Exception('only named routes can be added to pyroutes')
87 raise Exception('only named routes can be added to pyroutes')
87 self._jsroutes.append(args[0])
88 self._jsroutes.append(args[0])
88
89
89 super(JSRoutesMapper, self).connect(*args, **kw)
90 super(JSRoutesMapper, self).connect(*args, **kw)
90
91
91 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
92 """
93 """
93 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
94 ('user_profile', '/profile/%(username)s', ['username'])
95 ('user_profile', '/profile/%(username)s', ['username'])
95 """
96 """
96 routepath = route.routepath
97 routepath = route.routepath
97 def replace(matchobj):
98 def replace(matchobj):
98 if matchobj.group(1):
99 if matchobj.group(1):
99 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
100 else:
101 else:
101 return "%%(%s)s" % matchobj.group(2)
102 return "%%(%s)s" % matchobj.group(2)
102
103
103 routepath = self._argument_prog.sub(replace, routepath)
104 routepath = self._argument_prog.sub(replace, routepath)
104 return (
105 return (
105 route.name,
106 route.name,
106 routepath,
107 routepath,
107 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
108 for arg in self._argument_prog.findall(route.routepath)]
109 for arg in self._argument_prog.findall(route.routepath)]
109 )
110 )
110
111
111 def jsroutes(self):
112 def jsroutes(self):
112 """
113 """
113 Return a list of pyroutes.js compatible routes
114 Return a list of pyroutes.js compatible routes
114 """
115 """
115 for route_name in self._jsroutes:
116 for route_name in self._jsroutes:
116 yield self._extract_route_information(self._routenames[route_name])
117 yield self._extract_route_information(self._routenames[route_name])
117
118
118
119
119 def make_map(config):
120 def make_map(config):
120 """Create, configure and return the routes Mapper"""
121 """Create, configure and return the routes Mapper"""
121 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
122 always_scan=config['debug'])
123 always_scan=config['debug'])
123 rmap.minimization = False
124 rmap.minimization = False
124 rmap.explicit = False
125 rmap.explicit = False
125
126
126 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.lib.utils2 import str2bool
127 from rhodecode.model import repo, repo_group
128 from rhodecode.model import repo, repo_group
128
129
129 def check_repo(environ, match_dict):
130 def check_repo(environ, match_dict):
130 """
131 """
131 check for valid repository for proper 404 handling
132 check for valid repository for proper 404 handling
132
133
133 :param environ:
134 :param environ:
134 :param match_dict:
135 :param match_dict:
135 """
136 """
136 repo_name = match_dict.get('repo_name')
137 repo_name = match_dict.get('repo_name')
137
138
138 if match_dict.get('f_path'):
139 if match_dict.get('f_path'):
139 # fix for multiple initial slashes that causes errors
140 # fix for multiple initial slashes that causes errors
140 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
141 repo_model = repo.RepoModel()
142 repo_model = repo.RepoModel()
142 by_name_match = repo_model.get_by_repo_name(repo_name)
143 by_name_match = repo_model.get_by_repo_name(repo_name)
143 # if we match quickly from database, short circuit the operation,
144 # if we match quickly from database, short circuit the operation,
144 # and validate repo based on the type.
145 # and validate repo based on the type.
145 if by_name_match:
146 if by_name_match:
146 return True
147 return True
147
148
148 by_id_match = repo_model.get_repo_by_id(repo_name)
149 by_id_match = repo_model.get_repo_by_id(repo_name)
149 if by_id_match:
150 if by_id_match:
150 repo_name = by_id_match.repo_name
151 repo_name = by_id_match.repo_name
151 match_dict['repo_name'] = repo_name
152 match_dict['repo_name'] = repo_name
152 return True
153 return True
153
154
154 return False
155 return False
155
156
156 def check_group(environ, match_dict):
157 def check_group(environ, match_dict):
157 """
158 """
158 check for valid repository group path for proper 404 handling
159 check for valid repository group path for proper 404 handling
159
160
160 :param environ:
161 :param environ:
161 :param match_dict:
162 :param match_dict:
162 """
163 """
163 repo_group_name = match_dict.get('group_name')
164 repo_group_name = match_dict.get('group_name')
164 repo_group_model = repo_group.RepoGroupModel()
165 repo_group_model = repo_group.RepoGroupModel()
165 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
166 if by_name_match:
167 if by_name_match:
167 return True
168 return True
168
169
169 return False
170 return False
170
171
171 def check_user_group(environ, match_dict):
172 def check_user_group(environ, match_dict):
172 """
173 """
173 check for valid user group for proper 404 handling
174 check for valid user group for proper 404 handling
174
175
175 :param environ:
176 :param environ:
176 :param match_dict:
177 :param match_dict:
177 """
178 """
178 return True
179 return True
179
180
180 def check_int(environ, match_dict):
181 def check_int(environ, match_dict):
181 return match_dict.get('id').isdigit()
182 return match_dict.get('id').isdigit()
182
183
183
184
184 #==========================================================================
185 #==========================================================================
185 # CUSTOM ROUTES HERE
186 # CUSTOM ROUTES HERE
186 #==========================================================================
187 #==========================================================================
187
188
188 # MAIN PAGE
189 # MAIN PAGE
189 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
190 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
191 action='goto_switcher_data')
192 action='goto_switcher_data')
192 rmap.connect('repo_list_data', '/_repos', controller='home',
193 rmap.connect('repo_list_data', '/_repos', controller='home',
193 action='repo_list_data')
194 action='repo_list_data')
194
195
195 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
196 action='user_autocomplete_data', jsroute=True)
197 action='user_autocomplete_data', jsroute=True)
197 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 action='user_group_autocomplete_data')
199 action='user_group_autocomplete_data')
199
200
200 rmap.connect(
201 rmap.connect(
201 'user_profile', '/_profiles/{username}', controller='users',
202 'user_profile', '/_profiles/{username}', controller='users',
202 action='user_profile')
203 action='user_profile')
203
204
204 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
205 rmap.connect('rst_help',
206 rmap.connect('rst_help',
206 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
207 _static=True)
208 _static=True)
208 rmap.connect('markdown_help',
209 rmap.connect('markdown_help',
209 'http://daringfireball.net/projects/markdown/syntax',
210 'http://daringfireball.net/projects/markdown/syntax',
210 _static=True)
211 _static=True)
211 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
212 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
213 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
214 # TODO: anderson - making this a static link since redirect won't play
215 # TODO: anderson - making this a static link since redirect won't play
215 # nice with POST requests
216 # nice with POST requests
216 rmap.connect('enterprise_license_convert_from_old',
217 rmap.connect('enterprise_license_convert_from_old',
217 'https://rhodecode.com/u/license-upgrade',
218 'https://rhodecode.com/u/license-upgrade',
218 _static=True)
219 _static=True)
219
220
220 routing_links.connect_redirection_links(rmap)
221 routing_links.connect_redirection_links(rmap)
221
222
222 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
223 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
224
225
225 # ADMIN REPOSITORY ROUTES
226 # ADMIN REPOSITORY ROUTES
226 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
227 controller='admin/repos') as m:
228 controller='admin/repos') as m:
228 m.connect('repos', '/repos',
229 m.connect('repos', '/repos',
229 action='create', conditions={'method': ['POST']})
230 action='create', conditions={'method': ['POST']})
230 m.connect('repos', '/repos',
231 m.connect('repos', '/repos',
231 action='index', conditions={'method': ['GET']})
232 action='index', conditions={'method': ['GET']})
232 m.connect('new_repo', '/create_repository', jsroute=True,
233 m.connect('new_repo', '/create_repository', jsroute=True,
233 action='create_repository', conditions={'method': ['GET']})
234 action='create_repository', conditions={'method': ['GET']})
234 m.connect('/repos/{repo_name}',
235 m.connect('/repos/{repo_name}',
235 action='update', conditions={'method': ['PUT'],
236 action='update', conditions={'method': ['PUT'],
236 'function': check_repo},
237 'function': check_repo},
237 requirements=URL_NAME_REQUIREMENTS)
238 requirements=URL_NAME_REQUIREMENTS)
238 m.connect('delete_repo', '/repos/{repo_name}',
239 m.connect('delete_repo', '/repos/{repo_name}',
239 action='delete', conditions={'method': ['DELETE']},
240 action='delete', conditions={'method': ['DELETE']},
240 requirements=URL_NAME_REQUIREMENTS)
241 requirements=URL_NAME_REQUIREMENTS)
241 m.connect('repo', '/repos/{repo_name}',
242 m.connect('repo', '/repos/{repo_name}',
242 action='show', conditions={'method': ['GET'],
243 action='show', conditions={'method': ['GET'],
243 'function': check_repo},
244 'function': check_repo},
244 requirements=URL_NAME_REQUIREMENTS)
245 requirements=URL_NAME_REQUIREMENTS)
245
246
246 # ADMIN REPOSITORY GROUPS ROUTES
247 # ADMIN REPOSITORY GROUPS ROUTES
247 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
248 controller='admin/repo_groups') as m:
249 controller='admin/repo_groups') as m:
249 m.connect('repo_groups', '/repo_groups',
250 m.connect('repo_groups', '/repo_groups',
250 action='create', conditions={'method': ['POST']})
251 action='create', conditions={'method': ['POST']})
251 m.connect('repo_groups', '/repo_groups',
252 m.connect('repo_groups', '/repo_groups',
252 action='index', conditions={'method': ['GET']})
253 action='index', conditions={'method': ['GET']})
253 m.connect('new_repo_group', '/repo_groups/new',
254 m.connect('new_repo_group', '/repo_groups/new',
254 action='new', conditions={'method': ['GET']})
255 action='new', conditions={'method': ['GET']})
255 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 m.connect('update_repo_group', '/repo_groups/{group_name}',
256 action='update', conditions={'method': ['PUT'],
257 action='update', conditions={'method': ['PUT'],
257 'function': check_group},
258 'function': check_group},
258 requirements=URL_NAME_REQUIREMENTS)
259 requirements=URL_NAME_REQUIREMENTS)
259
260
260 # EXTRAS REPO GROUP ROUTES
261 # EXTRAS REPO GROUP ROUTES
261 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
262 action='edit',
263 action='edit',
263 conditions={'method': ['GET'], 'function': check_group},
264 conditions={'method': ['GET'], 'function': check_group},
264 requirements=URL_NAME_REQUIREMENTS)
265 requirements=URL_NAME_REQUIREMENTS)
265 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
266 action='edit',
267 action='edit',
267 conditions={'method': ['PUT'], 'function': check_group},
268 conditions={'method': ['PUT'], 'function': check_group},
268 requirements=URL_NAME_REQUIREMENTS)
269 requirements=URL_NAME_REQUIREMENTS)
269
270
270 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
271 action='edit_repo_group_advanced',
272 action='edit_repo_group_advanced',
272 conditions={'method': ['GET'], 'function': check_group},
273 conditions={'method': ['GET'], 'function': check_group},
273 requirements=URL_NAME_REQUIREMENTS)
274 requirements=URL_NAME_REQUIREMENTS)
274 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
275 action='edit_repo_group_advanced',
276 action='edit_repo_group_advanced',
276 conditions={'method': ['PUT'], 'function': check_group},
277 conditions={'method': ['PUT'], 'function': check_group},
277 requirements=URL_NAME_REQUIREMENTS)
278 requirements=URL_NAME_REQUIREMENTS)
278
279
279 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
280 action='edit_repo_group_perms',
281 action='edit_repo_group_perms',
281 conditions={'method': ['GET'], 'function': check_group},
282 conditions={'method': ['GET'], 'function': check_group},
282 requirements=URL_NAME_REQUIREMENTS)
283 requirements=URL_NAME_REQUIREMENTS)
283 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
284 action='update_perms',
285 action='update_perms',
285 conditions={'method': ['PUT'], 'function': check_group},
286 conditions={'method': ['PUT'], 'function': check_group},
286 requirements=URL_NAME_REQUIREMENTS)
287 requirements=URL_NAME_REQUIREMENTS)
287
288
288 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
289 action='delete', conditions={'method': ['DELETE'],
290 action='delete', conditions={'method': ['DELETE'],
290 'function': check_group},
291 'function': check_group},
291 requirements=URL_NAME_REQUIREMENTS)
292 requirements=URL_NAME_REQUIREMENTS)
292
293
293 # ADMIN USER ROUTES
294 # ADMIN USER ROUTES
294 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
295 controller='admin/users') as m:
296 controller='admin/users') as m:
296 m.connect('users', '/users',
297 m.connect('users', '/users',
297 action='create', conditions={'method': ['POST']})
298 action='create', conditions={'method': ['POST']})
298 m.connect('users', '/users',
299 m.connect('users', '/users',
299 action='index', conditions={'method': ['GET']})
300 action='index', conditions={'method': ['GET']})
300 m.connect('new_user', '/users/new',
301 m.connect('new_user', '/users/new',
301 action='new', conditions={'method': ['GET']})
302 action='new', conditions={'method': ['GET']})
302 m.connect('update_user', '/users/{user_id}',
303 m.connect('update_user', '/users/{user_id}',
303 action='update', conditions={'method': ['PUT']})
304 action='update', conditions={'method': ['PUT']})
304 m.connect('delete_user', '/users/{user_id}',
305 m.connect('delete_user', '/users/{user_id}',
305 action='delete', conditions={'method': ['DELETE']})
306 action='delete', conditions={'method': ['DELETE']})
306 m.connect('edit_user', '/users/{user_id}/edit',
307 m.connect('edit_user', '/users/{user_id}/edit',
307 action='edit', conditions={'method': ['GET']})
308 action='edit', conditions={'method': ['GET']})
308 m.connect('user', '/users/{user_id}',
309 m.connect('user', '/users/{user_id}',
309 action='show', conditions={'method': ['GET']})
310 action='show', conditions={'method': ['GET']})
310 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
311 action='reset_password', conditions={'method': ['POST']})
312 action='reset_password', conditions={'method': ['POST']})
312 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
313 action='create_personal_repo_group', conditions={'method': ['POST']})
314 action='create_personal_repo_group', conditions={'method': ['POST']})
314
315
315 # EXTRAS USER ROUTES
316 # EXTRAS USER ROUTES
316 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
317 action='edit_advanced', conditions={'method': ['GET']})
318 action='edit_advanced', conditions={'method': ['GET']})
318 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
319 action='update_advanced', conditions={'method': ['PUT']})
320 action='update_advanced', conditions={'method': ['PUT']})
320
321
321 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
322 action='edit_auth_tokens', conditions={'method': ['GET']})
323 action='edit_auth_tokens', conditions={'method': ['GET']})
323 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
324 action='add_auth_token', conditions={'method': ['PUT']})
325 action='add_auth_token', conditions={'method': ['PUT']})
325 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
326 action='delete_auth_token', conditions={'method': ['DELETE']})
327 action='delete_auth_token', conditions={'method': ['DELETE']})
327
328
328 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
329 action='edit_global_perms', conditions={'method': ['GET']})
330 action='edit_global_perms', conditions={'method': ['GET']})
330 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
331 action='update_global_perms', conditions={'method': ['PUT']})
332 action='update_global_perms', conditions={'method': ['PUT']})
332
333
333 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
334 action='edit_perms_summary', conditions={'method': ['GET']})
335 action='edit_perms_summary', conditions={'method': ['GET']})
335
336
336 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
337 action='edit_emails', conditions={'method': ['GET']})
338 action='edit_emails', conditions={'method': ['GET']})
338 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
339 action='add_email', conditions={'method': ['PUT']})
340 action='add_email', conditions={'method': ['PUT']})
340 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
341 action='delete_email', conditions={'method': ['DELETE']})
342 action='delete_email', conditions={'method': ['DELETE']})
342
343
343 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
344 action='edit_ips', conditions={'method': ['GET']})
345 action='edit_ips', conditions={'method': ['GET']})
345 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
346 action='add_ip', conditions={'method': ['PUT']})
347 action='add_ip', conditions={'method': ['PUT']})
347 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
348 action='delete_ip', conditions={'method': ['DELETE']})
349 action='delete_ip', conditions={'method': ['DELETE']})
349
350
350 # ADMIN USER GROUPS REST ROUTES
351 # ADMIN USER GROUPS REST ROUTES
351 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
352 controller='admin/user_groups') as m:
353 controller='admin/user_groups') as m:
353 m.connect('users_groups', '/user_groups',
354 m.connect('users_groups', '/user_groups',
354 action='create', conditions={'method': ['POST']})
355 action='create', conditions={'method': ['POST']})
355 m.connect('users_groups', '/user_groups',
356 m.connect('users_groups', '/user_groups',
356 action='index', conditions={'method': ['GET']})
357 action='index', conditions={'method': ['GET']})
357 m.connect('new_users_group', '/user_groups/new',
358 m.connect('new_users_group', '/user_groups/new',
358 action='new', conditions={'method': ['GET']})
359 action='new', conditions={'method': ['GET']})
359 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 m.connect('update_users_group', '/user_groups/{user_group_id}',
360 action='update', conditions={'method': ['PUT']})
361 action='update', conditions={'method': ['PUT']})
361 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
362 action='delete', conditions={'method': ['DELETE']})
363 action='delete', conditions={'method': ['DELETE']})
363 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
364 action='edit', conditions={'method': ['GET']},
365 action='edit', conditions={'method': ['GET']},
365 function=check_user_group)
366 function=check_user_group)
366
367
367 # EXTRAS USER GROUP ROUTES
368 # EXTRAS USER GROUP ROUTES
368 m.connect('edit_user_group_global_perms',
369 m.connect('edit_user_group_global_perms',
369 '/user_groups/{user_group_id}/edit/global_permissions',
370 '/user_groups/{user_group_id}/edit/global_permissions',
370 action='edit_global_perms', conditions={'method': ['GET']})
371 action='edit_global_perms', conditions={'method': ['GET']})
371 m.connect('edit_user_group_global_perms',
372 m.connect('edit_user_group_global_perms',
372 '/user_groups/{user_group_id}/edit/global_permissions',
373 '/user_groups/{user_group_id}/edit/global_permissions',
373 action='update_global_perms', conditions={'method': ['PUT']})
374 action='update_global_perms', conditions={'method': ['PUT']})
374 m.connect('edit_user_group_perms_summary',
375 m.connect('edit_user_group_perms_summary',
375 '/user_groups/{user_group_id}/edit/permissions_summary',
376 '/user_groups/{user_group_id}/edit/permissions_summary',
376 action='edit_perms_summary', conditions={'method': ['GET']})
377 action='edit_perms_summary', conditions={'method': ['GET']})
377
378
378 m.connect('edit_user_group_perms',
379 m.connect('edit_user_group_perms',
379 '/user_groups/{user_group_id}/edit/permissions',
380 '/user_groups/{user_group_id}/edit/permissions',
380 action='edit_perms', conditions={'method': ['GET']})
381 action='edit_perms', conditions={'method': ['GET']})
381 m.connect('edit_user_group_perms',
382 m.connect('edit_user_group_perms',
382 '/user_groups/{user_group_id}/edit/permissions',
383 '/user_groups/{user_group_id}/edit/permissions',
383 action='update_perms', conditions={'method': ['PUT']})
384 action='update_perms', conditions={'method': ['PUT']})
384
385
385 m.connect('edit_user_group_advanced',
386 m.connect('edit_user_group_advanced',
386 '/user_groups/{user_group_id}/edit/advanced',
387 '/user_groups/{user_group_id}/edit/advanced',
387 action='edit_advanced', conditions={'method': ['GET']})
388 action='edit_advanced', conditions={'method': ['GET']})
388
389
389 m.connect('edit_user_group_members',
390 m.connect('edit_user_group_members',
390 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
391 action='edit_members', conditions={'method': ['GET']})
392 action='edit_members', conditions={'method': ['GET']})
392
393
393 # ADMIN PERMISSIONS ROUTES
394 # ADMIN PERMISSIONS ROUTES
394 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
395 controller='admin/permissions') as m:
396 controller='admin/permissions') as m:
396 m.connect('admin_permissions_application', '/permissions/application',
397 m.connect('admin_permissions_application', '/permissions/application',
397 action='permission_application_update', conditions={'method': ['POST']})
398 action='permission_application_update', conditions={'method': ['POST']})
398 m.connect('admin_permissions_application', '/permissions/application',
399 m.connect('admin_permissions_application', '/permissions/application',
399 action='permission_application', conditions={'method': ['GET']})
400 action='permission_application', conditions={'method': ['GET']})
400
401
401 m.connect('admin_permissions_global', '/permissions/global',
402 m.connect('admin_permissions_global', '/permissions/global',
402 action='permission_global_update', conditions={'method': ['POST']})
403 action='permission_global_update', conditions={'method': ['POST']})
403 m.connect('admin_permissions_global', '/permissions/global',
404 m.connect('admin_permissions_global', '/permissions/global',
404 action='permission_global', conditions={'method': ['GET']})
405 action='permission_global', conditions={'method': ['GET']})
405
406
406 m.connect('admin_permissions_object', '/permissions/object',
407 m.connect('admin_permissions_object', '/permissions/object',
407 action='permission_objects_update', conditions={'method': ['POST']})
408 action='permission_objects_update', conditions={'method': ['POST']})
408 m.connect('admin_permissions_object', '/permissions/object',
409 m.connect('admin_permissions_object', '/permissions/object',
409 action='permission_objects', conditions={'method': ['GET']})
410 action='permission_objects', conditions={'method': ['GET']})
410
411
411 m.connect('admin_permissions_ips', '/permissions/ips',
412 m.connect('admin_permissions_ips', '/permissions/ips',
412 action='permission_ips', conditions={'method': ['POST']})
413 action='permission_ips', conditions={'method': ['POST']})
413 m.connect('admin_permissions_ips', '/permissions/ips',
414 m.connect('admin_permissions_ips', '/permissions/ips',
414 action='permission_ips', conditions={'method': ['GET']})
415 action='permission_ips', conditions={'method': ['GET']})
415
416
416 m.connect('admin_permissions_overview', '/permissions/overview',
417 m.connect('admin_permissions_overview', '/permissions/overview',
417 action='permission_perms', conditions={'method': ['GET']})
418 action='permission_perms', conditions={'method': ['GET']})
418
419
419 # ADMIN DEFAULTS REST ROUTES
420 # ADMIN DEFAULTS REST ROUTES
420 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
421 controller='admin/defaults') as m:
422 controller='admin/defaults') as m:
422 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 m.connect('admin_defaults_repositories', '/defaults/repositories',
423 action='update_repository_defaults', conditions={'method': ['POST']})
424 action='update_repository_defaults', conditions={'method': ['POST']})
424 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 m.connect('admin_defaults_repositories', '/defaults/repositories',
425 action='index', conditions={'method': ['GET']})
426 action='index', conditions={'method': ['GET']})
426
427
427 # ADMIN DEBUG STYLE ROUTES
428 # ADMIN DEBUG STYLE ROUTES
428 if str2bool(config.get('debug_style')):
429 if str2bool(config.get('debug_style')):
429 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
430 controller='debug_style') as m:
431 controller='debug_style') as m:
431 m.connect('debug_style_home', '',
432 m.connect('debug_style_home', '',
432 action='index', conditions={'method': ['GET']})
433 action='index', conditions={'method': ['GET']})
433 m.connect('debug_style_template', '/t/{t_path}',
434 m.connect('debug_style_template', '/t/{t_path}',
434 action='template', conditions={'method': ['GET']})
435 action='template', conditions={'method': ['GET']})
435
436
436 # ADMIN SETTINGS ROUTES
437 # ADMIN SETTINGS ROUTES
437 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
438 controller='admin/settings') as m:
439 controller='admin/settings') as m:
439
440
440 # default
441 # default
441 m.connect('admin_settings', '/settings',
442 m.connect('admin_settings', '/settings',
442 action='settings_global_update',
443 action='settings_global_update',
443 conditions={'method': ['POST']})
444 conditions={'method': ['POST']})
444 m.connect('admin_settings', '/settings',
445 m.connect('admin_settings', '/settings',
445 action='settings_global', conditions={'method': ['GET']})
446 action='settings_global', conditions={'method': ['GET']})
446
447
447 m.connect('admin_settings_vcs', '/settings/vcs',
448 m.connect('admin_settings_vcs', '/settings/vcs',
448 action='settings_vcs_update',
449 action='settings_vcs_update',
449 conditions={'method': ['POST']})
450 conditions={'method': ['POST']})
450 m.connect('admin_settings_vcs', '/settings/vcs',
451 m.connect('admin_settings_vcs', '/settings/vcs',
451 action='settings_vcs',
452 action='settings_vcs',
452 conditions={'method': ['GET']})
453 conditions={'method': ['GET']})
453 m.connect('admin_settings_vcs', '/settings/vcs',
454 m.connect('admin_settings_vcs', '/settings/vcs',
454 action='delete_svn_pattern',
455 action='delete_svn_pattern',
455 conditions={'method': ['DELETE']})
456 conditions={'method': ['DELETE']})
456
457
457 m.connect('admin_settings_mapping', '/settings/mapping',
458 m.connect('admin_settings_mapping', '/settings/mapping',
458 action='settings_mapping_update',
459 action='settings_mapping_update',
459 conditions={'method': ['POST']})
460 conditions={'method': ['POST']})
460 m.connect('admin_settings_mapping', '/settings/mapping',
461 m.connect('admin_settings_mapping', '/settings/mapping',
461 action='settings_mapping', conditions={'method': ['GET']})
462 action='settings_mapping', conditions={'method': ['GET']})
462
463
463 m.connect('admin_settings_global', '/settings/global',
464 m.connect('admin_settings_global', '/settings/global',
464 action='settings_global_update',
465 action='settings_global_update',
465 conditions={'method': ['POST']})
466 conditions={'method': ['POST']})
466 m.connect('admin_settings_global', '/settings/global',
467 m.connect('admin_settings_global', '/settings/global',
467 action='settings_global', conditions={'method': ['GET']})
468 action='settings_global', conditions={'method': ['GET']})
468
469
469 m.connect('admin_settings_visual', '/settings/visual',
470 m.connect('admin_settings_visual', '/settings/visual',
470 action='settings_visual_update',
471 action='settings_visual_update',
471 conditions={'method': ['POST']})
472 conditions={'method': ['POST']})
472 m.connect('admin_settings_visual', '/settings/visual',
473 m.connect('admin_settings_visual', '/settings/visual',
473 action='settings_visual', conditions={'method': ['GET']})
474 action='settings_visual', conditions={'method': ['GET']})
474
475
475 m.connect('admin_settings_issuetracker',
476 m.connect('admin_settings_issuetracker',
476 '/settings/issue-tracker', action='settings_issuetracker',
477 '/settings/issue-tracker', action='settings_issuetracker',
477 conditions={'method': ['GET']})
478 conditions={'method': ['GET']})
478 m.connect('admin_settings_issuetracker_save',
479 m.connect('admin_settings_issuetracker_save',
479 '/settings/issue-tracker/save',
480 '/settings/issue-tracker/save',
480 action='settings_issuetracker_save',
481 action='settings_issuetracker_save',
481 conditions={'method': ['POST']})
482 conditions={'method': ['POST']})
482 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
483 action='settings_issuetracker_test',
484 action='settings_issuetracker_test',
484 conditions={'method': ['POST']})
485 conditions={'method': ['POST']})
485 m.connect('admin_issuetracker_delete',
486 m.connect('admin_issuetracker_delete',
486 '/settings/issue-tracker/delete',
487 '/settings/issue-tracker/delete',
487 action='settings_issuetracker_delete',
488 action='settings_issuetracker_delete',
488 conditions={'method': ['DELETE']})
489 conditions={'method': ['DELETE']})
489
490
490 m.connect('admin_settings_email', '/settings/email',
491 m.connect('admin_settings_email', '/settings/email',
491 action='settings_email_update',
492 action='settings_email_update',
492 conditions={'method': ['POST']})
493 conditions={'method': ['POST']})
493 m.connect('admin_settings_email', '/settings/email',
494 m.connect('admin_settings_email', '/settings/email',
494 action='settings_email', conditions={'method': ['GET']})
495 action='settings_email', conditions={'method': ['GET']})
495
496
496 m.connect('admin_settings_hooks', '/settings/hooks',
497 m.connect('admin_settings_hooks', '/settings/hooks',
497 action='settings_hooks_update',
498 action='settings_hooks_update',
498 conditions={'method': ['POST', 'DELETE']})
499 conditions={'method': ['POST', 'DELETE']})
499 m.connect('admin_settings_hooks', '/settings/hooks',
500 m.connect('admin_settings_hooks', '/settings/hooks',
500 action='settings_hooks', conditions={'method': ['GET']})
501 action='settings_hooks', conditions={'method': ['GET']})
501
502
502 m.connect('admin_settings_search', '/settings/search',
503 m.connect('admin_settings_search', '/settings/search',
503 action='settings_search', conditions={'method': ['GET']})
504 action='settings_search', conditions={'method': ['GET']})
504
505
505 m.connect('admin_settings_system', '/settings/system',
506 m.connect('admin_settings_system', '/settings/system',
506 action='settings_system', conditions={'method': ['GET']})
507 action='settings_system', conditions={'method': ['GET']})
507
508
508 m.connect('admin_settings_system_update', '/settings/system/updates',
509 m.connect('admin_settings_system_update', '/settings/system/updates',
509 action='settings_system_update', conditions={'method': ['GET']})
510 action='settings_system_update', conditions={'method': ['GET']})
510
511
511 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 m.connect('admin_settings_supervisor', '/settings/supervisor',
512 action='settings_supervisor', conditions={'method': ['GET']})
513 action='settings_supervisor', conditions={'method': ['GET']})
513 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
514 action='settings_supervisor_log', conditions={'method': ['GET']})
515 action='settings_supervisor_log', conditions={'method': ['GET']})
515
516
516 m.connect('admin_settings_labs', '/settings/labs',
517 m.connect('admin_settings_labs', '/settings/labs',
517 action='settings_labs_update',
518 action='settings_labs_update',
518 conditions={'method': ['POST']})
519 conditions={'method': ['POST']})
519 m.connect('admin_settings_labs', '/settings/labs',
520 m.connect('admin_settings_labs', '/settings/labs',
520 action='settings_labs', conditions={'method': ['GET']})
521 action='settings_labs', conditions={'method': ['GET']})
521
522
522 # ADMIN MY ACCOUNT
523 # ADMIN MY ACCOUNT
523 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
524 controller='admin/my_account') as m:
525 controller='admin/my_account') as m:
525
526
526 m.connect('my_account', '/my_account',
527 m.connect('my_account', '/my_account',
527 action='my_account', conditions={'method': ['GET']})
528 action='my_account', conditions={'method': ['GET']})
528 m.connect('my_account_edit', '/my_account/edit',
529 m.connect('my_account_edit', '/my_account/edit',
529 action='my_account_edit', conditions={'method': ['GET']})
530 action='my_account_edit', conditions={'method': ['GET']})
530 m.connect('my_account', '/my_account',
531 m.connect('my_account', '/my_account',
531 action='my_account_update', conditions={'method': ['POST']})
532 action='my_account_update', conditions={'method': ['POST']})
532
533
533 m.connect('my_account_password', '/my_account/password',
534 m.connect('my_account_password', '/my_account/password',
534 action='my_account_password', conditions={'method': ['GET', 'POST']})
535 action='my_account_password', conditions={'method': ['GET', 'POST']})
535
536
536 m.connect('my_account_repos', '/my_account/repos',
537 m.connect('my_account_repos', '/my_account/repos',
537 action='my_account_repos', conditions={'method': ['GET']})
538 action='my_account_repos', conditions={'method': ['GET']})
538
539
539 m.connect('my_account_watched', '/my_account/watched',
540 m.connect('my_account_watched', '/my_account/watched',
540 action='my_account_watched', conditions={'method': ['GET']})
541 action='my_account_watched', conditions={'method': ['GET']})
541
542
542 m.connect('my_account_pullrequests', '/my_account/pull_requests',
543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
543 action='my_account_pullrequests', conditions={'method': ['GET']})
544 action='my_account_pullrequests', conditions={'method': ['GET']})
544
545
545 m.connect('my_account_perms', '/my_account/perms',
546 m.connect('my_account_perms', '/my_account/perms',
546 action='my_account_perms', conditions={'method': ['GET']})
547 action='my_account_perms', conditions={'method': ['GET']})
547
548
548 m.connect('my_account_emails', '/my_account/emails',
549 m.connect('my_account_emails', '/my_account/emails',
549 action='my_account_emails', conditions={'method': ['GET']})
550 action='my_account_emails', conditions={'method': ['GET']})
550 m.connect('my_account_emails', '/my_account/emails',
551 m.connect('my_account_emails', '/my_account/emails',
551 action='my_account_emails_add', conditions={'method': ['POST']})
552 action='my_account_emails_add', conditions={'method': ['POST']})
552 m.connect('my_account_emails', '/my_account/emails',
553 m.connect('my_account_emails', '/my_account/emails',
553 action='my_account_emails_delete', conditions={'method': ['DELETE']})
554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
554
555
555 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
556 action='my_account_auth_tokens', conditions={'method': ['GET']})
557 action='my_account_auth_tokens', conditions={'method': ['GET']})
557 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
558 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
559 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
560 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
561 m.connect('my_account_notifications', '/my_account/notifications',
562 m.connect('my_account_notifications', '/my_account/notifications',
562 action='my_notifications',
563 action='my_notifications',
563 conditions={'method': ['GET']})
564 conditions={'method': ['GET']})
564 m.connect('my_account_notifications_toggle_visibility',
565 m.connect('my_account_notifications_toggle_visibility',
565 '/my_account/toggle_visibility',
566 '/my_account/toggle_visibility',
566 action='my_notifications_toggle_visibility',
567 action='my_notifications_toggle_visibility',
567 conditions={'method': ['POST']})
568 conditions={'method': ['POST']})
568
569
569 # NOTIFICATION REST ROUTES
570 # NOTIFICATION REST ROUTES
570 with rmap.submapper(path_prefix=ADMIN_PREFIX,
571 with rmap.submapper(path_prefix=ADMIN_PREFIX,
571 controller='admin/notifications') as m:
572 controller='admin/notifications') as m:
572 m.connect('notifications', '/notifications',
573 m.connect('notifications', '/notifications',
573 action='index', conditions={'method': ['GET']})
574 action='index', conditions={'method': ['GET']})
574 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
575 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
575 action='mark_all_read', conditions={'method': ['POST']})
576 action='mark_all_read', conditions={'method': ['POST']})
576 m.connect('/notifications/{notification_id}',
577 m.connect('/notifications/{notification_id}',
577 action='update', conditions={'method': ['PUT']})
578 action='update', conditions={'method': ['PUT']})
578 m.connect('/notifications/{notification_id}',
579 m.connect('/notifications/{notification_id}',
579 action='delete', conditions={'method': ['DELETE']})
580 action='delete', conditions={'method': ['DELETE']})
580 m.connect('notification', '/notifications/{notification_id}',
581 m.connect('notification', '/notifications/{notification_id}',
581 action='show', conditions={'method': ['GET']})
582 action='show', conditions={'method': ['GET']})
582
583
583 # ADMIN GIST
584 # ADMIN GIST
584 with rmap.submapper(path_prefix=ADMIN_PREFIX,
585 with rmap.submapper(path_prefix=ADMIN_PREFIX,
585 controller='admin/gists') as m:
586 controller='admin/gists') as m:
586 m.connect('gists', '/gists',
587 m.connect('gists', '/gists',
587 action='create', conditions={'method': ['POST']})
588 action='create', conditions={'method': ['POST']})
588 m.connect('gists', '/gists', jsroute=True,
589 m.connect('gists', '/gists', jsroute=True,
589 action='index', conditions={'method': ['GET']})
590 action='index', conditions={'method': ['GET']})
590 m.connect('new_gist', '/gists/new', jsroute=True,
591 m.connect('new_gist', '/gists/new', jsroute=True,
591 action='new', conditions={'method': ['GET']})
592 action='new', conditions={'method': ['GET']})
592
593
593 m.connect('/gists/{gist_id}',
594 m.connect('/gists/{gist_id}',
594 action='delete', conditions={'method': ['DELETE']})
595 action='delete', conditions={'method': ['DELETE']})
595 m.connect('edit_gist', '/gists/{gist_id}/edit',
596 m.connect('edit_gist', '/gists/{gist_id}/edit',
596 action='edit_form', conditions={'method': ['GET']})
597 action='edit_form', conditions={'method': ['GET']})
597 m.connect('edit_gist', '/gists/{gist_id}/edit',
598 m.connect('edit_gist', '/gists/{gist_id}/edit',
598 action='edit', conditions={'method': ['POST']})
599 action='edit', conditions={'method': ['POST']})
599 m.connect(
600 m.connect(
600 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
601 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
601 action='check_revision', conditions={'method': ['GET']})
602 action='check_revision', conditions={'method': ['GET']})
602
603
603 m.connect('gist', '/gists/{gist_id}',
604 m.connect('gist', '/gists/{gist_id}',
604 action='show', conditions={'method': ['GET']})
605 action='show', conditions={'method': ['GET']})
605 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
606 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
606 revision='tip',
607 revision='tip',
607 action='show', conditions={'method': ['GET']})
608 action='show', conditions={'method': ['GET']})
608 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
609 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
609 revision='tip',
610 revision='tip',
610 action='show', conditions={'method': ['GET']})
611 action='show', conditions={'method': ['GET']})
611 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
612 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
612 revision='tip',
613 revision='tip',
613 action='show', conditions={'method': ['GET']},
614 action='show', conditions={'method': ['GET']},
614 requirements=URL_NAME_REQUIREMENTS)
615 requirements=URL_NAME_REQUIREMENTS)
615
616
616 # ADMIN MAIN PAGES
617 # ADMIN MAIN PAGES
617 with rmap.submapper(path_prefix=ADMIN_PREFIX,
618 with rmap.submapper(path_prefix=ADMIN_PREFIX,
618 controller='admin/admin') as m:
619 controller='admin/admin') as m:
619 m.connect('admin_home', '', action='index')
620 m.connect('admin_home', '', action='index')
620 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
621 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
621 action='add_repo')
622 action='add_repo')
622 m.connect(
623 m.connect(
623 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
624 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
624 action='pull_requests')
625 action='pull_requests')
625 m.connect(
626 m.connect(
626 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
627 'pull_requests_global', '/pull-requests/{pull_request_id:[0-9]+}',
627 action='pull_requests')
628 action='pull_requests')
628
629
629
630
630 # USER JOURNAL
631 # USER JOURNAL
631 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
632 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
632 controller='journal', action='index')
633 controller='journal', action='index')
633 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
634 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
634 controller='journal', action='journal_rss')
635 controller='journal', action='journal_rss')
635 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
636 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
636 controller='journal', action='journal_atom')
637 controller='journal', action='journal_atom')
637
638
638 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
639 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
639 controller='journal', action='public_journal')
640 controller='journal', action='public_journal')
640
641
641 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
642 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
642 controller='journal', action='public_journal_rss')
643 controller='journal', action='public_journal_rss')
643
644
644 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
645 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
645 controller='journal', action='public_journal_rss')
646 controller='journal', action='public_journal_rss')
646
647
647 rmap.connect('public_journal_atom',
648 rmap.connect('public_journal_atom',
648 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
649 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
649 action='public_journal_atom')
650 action='public_journal_atom')
650
651
651 rmap.connect('public_journal_atom_old',
652 rmap.connect('public_journal_atom_old',
652 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
653 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
653 action='public_journal_atom')
654 action='public_journal_atom')
654
655
655 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
656 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
656 controller='journal', action='toggle_following', jsroute=True,
657 controller='journal', action='toggle_following', jsroute=True,
657 conditions={'method': ['POST']})
658 conditions={'method': ['POST']})
658
659
659 # FULL TEXT SEARCH
660 # FULL TEXT SEARCH
660 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
661 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
661 controller='search')
662 controller='search')
662 rmap.connect('search_repo_home', '/{repo_name}/search',
663 rmap.connect('search_repo_home', '/{repo_name}/search',
663 controller='search',
664 controller='search',
664 action='index',
665 action='index',
665 conditions={'function': check_repo},
666 conditions={'function': check_repo},
666 requirements=URL_NAME_REQUIREMENTS)
667 requirements=URL_NAME_REQUIREMENTS)
667
668
668 # FEEDS
669 # FEEDS
669 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
670 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
670 controller='feed', action='rss',
671 controller='feed', action='rss',
671 conditions={'function': check_repo},
672 conditions={'function': check_repo},
672 requirements=URL_NAME_REQUIREMENTS)
673 requirements=URL_NAME_REQUIREMENTS)
673
674
674 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
675 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
675 controller='feed', action='atom',
676 controller='feed', action='atom',
676 conditions={'function': check_repo},
677 conditions={'function': check_repo},
677 requirements=URL_NAME_REQUIREMENTS)
678 requirements=URL_NAME_REQUIREMENTS)
678
679
679 #==========================================================================
680 #==========================================================================
680 # REPOSITORY ROUTES
681 # REPOSITORY ROUTES
681 #==========================================================================
682 #==========================================================================
682
683
683 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
684 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
684 controller='admin/repos', action='repo_creating',
685 controller='admin/repos', action='repo_creating',
685 requirements=URL_NAME_REQUIREMENTS)
686 requirements=URL_NAME_REQUIREMENTS)
686 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
687 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
687 controller='admin/repos', action='repo_check',
688 controller='admin/repos', action='repo_check',
688 requirements=URL_NAME_REQUIREMENTS)
689 requirements=URL_NAME_REQUIREMENTS)
689
690
690 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
691 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
691 controller='summary', action='repo_stats',
692 controller='summary', action='repo_stats',
692 conditions={'function': check_repo},
693 conditions={'function': check_repo},
693 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
694 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
694
695
695 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
696 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
696 controller='summary', action='repo_refs_data', jsroute=True,
697 controller='summary', action='repo_refs_data', jsroute=True,
697 requirements=URL_NAME_REQUIREMENTS)
698 requirements=URL_NAME_REQUIREMENTS)
698 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
699 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
699 controller='summary', action='repo_refs_changelog_data',
700 controller='summary', action='repo_refs_changelog_data',
700 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
701 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
701
702
702 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
703 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
703 controller='changeset', revision='tip', jsroute=True,
704 controller='changeset', revision='tip', jsroute=True,
704 conditions={'function': check_repo},
705 conditions={'function': check_repo},
705 requirements=URL_NAME_REQUIREMENTS)
706 requirements=URL_NAME_REQUIREMENTS)
706 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
707 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
707 controller='changeset', revision='tip', action='changeset_children',
708 controller='changeset', revision='tip', action='changeset_children',
708 conditions={'function': check_repo},
709 conditions={'function': check_repo},
709 requirements=URL_NAME_REQUIREMENTS)
710 requirements=URL_NAME_REQUIREMENTS)
710 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
711 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
711 controller='changeset', revision='tip', action='changeset_parents',
712 controller='changeset', revision='tip', action='changeset_parents',
712 conditions={'function': check_repo},
713 conditions={'function': check_repo},
713 requirements=URL_NAME_REQUIREMENTS)
714 requirements=URL_NAME_REQUIREMENTS)
714
715
715 # repo edit options
716 # repo edit options
716 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
717 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
717 controller='admin/repos', action='edit',
718 controller='admin/repos', action='edit',
718 conditions={'method': ['GET'], 'function': check_repo},
719 conditions={'method': ['GET'], 'function': check_repo},
719 requirements=URL_NAME_REQUIREMENTS)
720 requirements=URL_NAME_REQUIREMENTS)
720
721
721 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
722 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
722 jsroute=True,
723 jsroute=True,
723 controller='admin/repos', action='edit_permissions',
724 controller='admin/repos', action='edit_permissions',
724 conditions={'method': ['GET'], 'function': check_repo},
725 conditions={'method': ['GET'], 'function': check_repo},
725 requirements=URL_NAME_REQUIREMENTS)
726 requirements=URL_NAME_REQUIREMENTS)
726 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
727 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
727 controller='admin/repos', action='edit_permissions_update',
728 controller='admin/repos', action='edit_permissions_update',
728 conditions={'method': ['PUT'], 'function': check_repo},
729 conditions={'method': ['PUT'], 'function': check_repo},
729 requirements=URL_NAME_REQUIREMENTS)
730 requirements=URL_NAME_REQUIREMENTS)
730
731
731 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
732 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
732 controller='admin/repos', action='edit_fields',
733 controller='admin/repos', action='edit_fields',
733 conditions={'method': ['GET'], 'function': check_repo},
734 conditions={'method': ['GET'], 'function': check_repo},
734 requirements=URL_NAME_REQUIREMENTS)
735 requirements=URL_NAME_REQUIREMENTS)
735 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
736 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
736 controller='admin/repos', action='create_repo_field',
737 controller='admin/repos', action='create_repo_field',
737 conditions={'method': ['PUT'], 'function': check_repo},
738 conditions={'method': ['PUT'], 'function': check_repo},
738 requirements=URL_NAME_REQUIREMENTS)
739 requirements=URL_NAME_REQUIREMENTS)
739 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
740 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
740 controller='admin/repos', action='delete_repo_field',
741 controller='admin/repos', action='delete_repo_field',
741 conditions={'method': ['DELETE'], 'function': check_repo},
742 conditions={'method': ['DELETE'], 'function': check_repo},
742 requirements=URL_NAME_REQUIREMENTS)
743 requirements=URL_NAME_REQUIREMENTS)
743
744
744 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
745 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
745 controller='admin/repos', action='edit_advanced',
746 controller='admin/repos', action='edit_advanced',
746 conditions={'method': ['GET'], 'function': check_repo},
747 conditions={'method': ['GET'], 'function': check_repo},
747 requirements=URL_NAME_REQUIREMENTS)
748 requirements=URL_NAME_REQUIREMENTS)
748
749
749 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
750 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
750 controller='admin/repos', action='edit_advanced_locking',
751 controller='admin/repos', action='edit_advanced_locking',
751 conditions={'method': ['PUT'], 'function': check_repo},
752 conditions={'method': ['PUT'], 'function': check_repo},
752 requirements=URL_NAME_REQUIREMENTS)
753 requirements=URL_NAME_REQUIREMENTS)
753 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
754 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
754 controller='admin/repos', action='toggle_locking',
755 controller='admin/repos', action='toggle_locking',
755 conditions={'method': ['GET'], 'function': check_repo},
756 conditions={'method': ['GET'], 'function': check_repo},
756 requirements=URL_NAME_REQUIREMENTS)
757 requirements=URL_NAME_REQUIREMENTS)
757
758
758 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
759 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
759 controller='admin/repos', action='edit_advanced_journal',
760 controller='admin/repos', action='edit_advanced_journal',
760 conditions={'method': ['PUT'], 'function': check_repo},
761 conditions={'method': ['PUT'], 'function': check_repo},
761 requirements=URL_NAME_REQUIREMENTS)
762 requirements=URL_NAME_REQUIREMENTS)
762
763
763 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
764 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
764 controller='admin/repos', action='edit_advanced_fork',
765 controller='admin/repos', action='edit_advanced_fork',
765 conditions={'method': ['PUT'], 'function': check_repo},
766 conditions={'method': ['PUT'], 'function': check_repo},
766 requirements=URL_NAME_REQUIREMENTS)
767 requirements=URL_NAME_REQUIREMENTS)
767
768
768 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
769 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
769 controller='admin/repos', action='edit_caches_form',
770 controller='admin/repos', action='edit_caches_form',
770 conditions={'method': ['GET'], 'function': check_repo},
771 conditions={'method': ['GET'], 'function': check_repo},
771 requirements=URL_NAME_REQUIREMENTS)
772 requirements=URL_NAME_REQUIREMENTS)
772 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
773 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
773 controller='admin/repos', action='edit_caches',
774 controller='admin/repos', action='edit_caches',
774 conditions={'method': ['PUT'], 'function': check_repo},
775 conditions={'method': ['PUT'], 'function': check_repo},
775 requirements=URL_NAME_REQUIREMENTS)
776 requirements=URL_NAME_REQUIREMENTS)
776
777
777 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
778 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
778 controller='admin/repos', action='edit_remote_form',
779 controller='admin/repos', action='edit_remote_form',
779 conditions={'method': ['GET'], 'function': check_repo},
780 conditions={'method': ['GET'], 'function': check_repo},
780 requirements=URL_NAME_REQUIREMENTS)
781 requirements=URL_NAME_REQUIREMENTS)
781 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
782 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
782 controller='admin/repos', action='edit_remote',
783 controller='admin/repos', action='edit_remote',
783 conditions={'method': ['PUT'], 'function': check_repo},
784 conditions={'method': ['PUT'], 'function': check_repo},
784 requirements=URL_NAME_REQUIREMENTS)
785 requirements=URL_NAME_REQUIREMENTS)
785
786
786 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
787 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
787 controller='admin/repos', action='edit_statistics_form',
788 controller='admin/repos', action='edit_statistics_form',
788 conditions={'method': ['GET'], 'function': check_repo},
789 conditions={'method': ['GET'], 'function': check_repo},
789 requirements=URL_NAME_REQUIREMENTS)
790 requirements=URL_NAME_REQUIREMENTS)
790 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
791 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
791 controller='admin/repos', action='edit_statistics',
792 controller='admin/repos', action='edit_statistics',
792 conditions={'method': ['PUT'], 'function': check_repo},
793 conditions={'method': ['PUT'], 'function': check_repo},
793 requirements=URL_NAME_REQUIREMENTS)
794 requirements=URL_NAME_REQUIREMENTS)
794 rmap.connect('repo_settings_issuetracker',
795 rmap.connect('repo_settings_issuetracker',
795 '/{repo_name}/settings/issue-tracker',
796 '/{repo_name}/settings/issue-tracker',
796 controller='admin/repos', action='repo_issuetracker',
797 controller='admin/repos', action='repo_issuetracker',
797 conditions={'method': ['GET'], 'function': check_repo},
798 conditions={'method': ['GET'], 'function': check_repo},
798 requirements=URL_NAME_REQUIREMENTS)
799 requirements=URL_NAME_REQUIREMENTS)
799 rmap.connect('repo_issuetracker_test',
800 rmap.connect('repo_issuetracker_test',
800 '/{repo_name}/settings/issue-tracker/test',
801 '/{repo_name}/settings/issue-tracker/test',
801 controller='admin/repos', action='repo_issuetracker_test',
802 controller='admin/repos', action='repo_issuetracker_test',
802 conditions={'method': ['POST'], 'function': check_repo},
803 conditions={'method': ['POST'], 'function': check_repo},
803 requirements=URL_NAME_REQUIREMENTS)
804 requirements=URL_NAME_REQUIREMENTS)
804 rmap.connect('repo_issuetracker_delete',
805 rmap.connect('repo_issuetracker_delete',
805 '/{repo_name}/settings/issue-tracker/delete',
806 '/{repo_name}/settings/issue-tracker/delete',
806 controller='admin/repos', action='repo_issuetracker_delete',
807 controller='admin/repos', action='repo_issuetracker_delete',
807 conditions={'method': ['DELETE'], 'function': check_repo},
808 conditions={'method': ['DELETE'], 'function': check_repo},
808 requirements=URL_NAME_REQUIREMENTS)
809 requirements=URL_NAME_REQUIREMENTS)
809 rmap.connect('repo_issuetracker_save',
810 rmap.connect('repo_issuetracker_save',
810 '/{repo_name}/settings/issue-tracker/save',
811 '/{repo_name}/settings/issue-tracker/save',
811 controller='admin/repos', action='repo_issuetracker_save',
812 controller='admin/repos', action='repo_issuetracker_save',
812 conditions={'method': ['POST'], 'function': check_repo},
813 conditions={'method': ['POST'], 'function': check_repo},
813 requirements=URL_NAME_REQUIREMENTS)
814 requirements=URL_NAME_REQUIREMENTS)
814 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
815 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
815 controller='admin/repos', action='repo_settings_vcs_update',
816 controller='admin/repos', action='repo_settings_vcs_update',
816 conditions={'method': ['POST'], 'function': check_repo},
817 conditions={'method': ['POST'], 'function': check_repo},
817 requirements=URL_NAME_REQUIREMENTS)
818 requirements=URL_NAME_REQUIREMENTS)
818 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
819 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
819 controller='admin/repos', action='repo_settings_vcs',
820 controller='admin/repos', action='repo_settings_vcs',
820 conditions={'method': ['GET'], 'function': check_repo},
821 conditions={'method': ['GET'], 'function': check_repo},
821 requirements=URL_NAME_REQUIREMENTS)
822 requirements=URL_NAME_REQUIREMENTS)
822 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
823 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
823 controller='admin/repos', action='repo_delete_svn_pattern',
824 controller='admin/repos', action='repo_delete_svn_pattern',
824 conditions={'method': ['DELETE'], 'function': check_repo},
825 conditions={'method': ['DELETE'], 'function': check_repo},
825 requirements=URL_NAME_REQUIREMENTS)
826 requirements=URL_NAME_REQUIREMENTS)
826
827
827 # still working url for backward compat.
828 # still working url for backward compat.
828 rmap.connect('raw_changeset_home_depraced',
829 rmap.connect('raw_changeset_home_depraced',
829 '/{repo_name}/raw-changeset/{revision}',
830 '/{repo_name}/raw-changeset/{revision}',
830 controller='changeset', action='changeset_raw',
831 controller='changeset', action='changeset_raw',
831 revision='tip', conditions={'function': check_repo},
832 revision='tip', conditions={'function': check_repo},
832 requirements=URL_NAME_REQUIREMENTS)
833 requirements=URL_NAME_REQUIREMENTS)
833
834
834 # new URLs
835 # new URLs
835 rmap.connect('changeset_raw_home',
836 rmap.connect('changeset_raw_home',
836 '/{repo_name}/changeset-diff/{revision}',
837 '/{repo_name}/changeset-diff/{revision}',
837 controller='changeset', action='changeset_raw',
838 controller='changeset', action='changeset_raw',
838 revision='tip', conditions={'function': check_repo},
839 revision='tip', conditions={'function': check_repo},
839 requirements=URL_NAME_REQUIREMENTS)
840 requirements=URL_NAME_REQUIREMENTS)
840
841
841 rmap.connect('changeset_patch_home',
842 rmap.connect('changeset_patch_home',
842 '/{repo_name}/changeset-patch/{revision}',
843 '/{repo_name}/changeset-patch/{revision}',
843 controller='changeset', action='changeset_patch',
844 controller='changeset', action='changeset_patch',
844 revision='tip', conditions={'function': check_repo},
845 revision='tip', conditions={'function': check_repo},
845 requirements=URL_NAME_REQUIREMENTS)
846 requirements=URL_NAME_REQUIREMENTS)
846
847
847 rmap.connect('changeset_download_home',
848 rmap.connect('changeset_download_home',
848 '/{repo_name}/changeset-download/{revision}',
849 '/{repo_name}/changeset-download/{revision}',
849 controller='changeset', action='changeset_download',
850 controller='changeset', action='changeset_download',
850 revision='tip', conditions={'function': check_repo},
851 revision='tip', conditions={'function': check_repo},
851 requirements=URL_NAME_REQUIREMENTS)
852 requirements=URL_NAME_REQUIREMENTS)
852
853
853 rmap.connect('changeset_comment',
854 rmap.connect('changeset_comment',
854 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
855 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
855 controller='changeset', revision='tip', action='comment',
856 controller='changeset', revision='tip', action='comment',
856 conditions={'function': check_repo},
857 conditions={'function': check_repo},
857 requirements=URL_NAME_REQUIREMENTS)
858 requirements=URL_NAME_REQUIREMENTS)
858
859
859 rmap.connect('changeset_comment_preview',
860 rmap.connect('changeset_comment_preview',
860 '/{repo_name}/changeset/comment/preview', jsroute=True,
861 '/{repo_name}/changeset/comment/preview', jsroute=True,
861 controller='changeset', action='preview_comment',
862 controller='changeset', action='preview_comment',
862 conditions={'function': check_repo, 'method': ['POST']},
863 conditions={'function': check_repo, 'method': ['POST']},
863 requirements=URL_NAME_REQUIREMENTS)
864 requirements=URL_NAME_REQUIREMENTS)
864
865
865 rmap.connect('changeset_comment_delete',
866 rmap.connect('changeset_comment_delete',
866 '/{repo_name}/changeset/comment/{comment_id}/delete',
867 '/{repo_name}/changeset/comment/{comment_id}/delete',
867 controller='changeset', action='delete_comment',
868 controller='changeset', action='delete_comment',
868 conditions={'function': check_repo, 'method': ['DELETE']},
869 conditions={'function': check_repo, 'method': ['DELETE']},
869 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
870 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
870
871
871 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
872 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
872 controller='changeset', action='changeset_info',
873 controller='changeset', action='changeset_info',
873 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
874 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
874
875
875 rmap.connect('compare_home',
876 rmap.connect('compare_home',
876 '/{repo_name}/compare',
877 '/{repo_name}/compare',
877 controller='compare', action='index',
878 controller='compare', action='index',
878 conditions={'function': check_repo},
879 conditions={'function': check_repo},
879 requirements=URL_NAME_REQUIREMENTS)
880 requirements=URL_NAME_REQUIREMENTS)
880
881
881 rmap.connect('compare_url',
882 rmap.connect('compare_url',
882 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
883 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
883 controller='compare', action='compare',
884 controller='compare', action='compare',
884 conditions={'function': check_repo},
885 conditions={'function': check_repo},
885 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
886 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
886
887
887 rmap.connect('pullrequest_home',
888 rmap.connect('pullrequest_home',
888 '/{repo_name}/pull-request/new', controller='pullrequests',
889 '/{repo_name}/pull-request/new', controller='pullrequests',
889 action='index', conditions={'function': check_repo,
890 action='index', conditions={'function': check_repo,
890 'method': ['GET']},
891 'method': ['GET']},
891 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
892 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
892
893
893 rmap.connect('pullrequest',
894 rmap.connect('pullrequest',
894 '/{repo_name}/pull-request/new', controller='pullrequests',
895 '/{repo_name}/pull-request/new', controller='pullrequests',
895 action='create', conditions={'function': check_repo,
896 action='create', conditions={'function': check_repo,
896 'method': ['POST']},
897 'method': ['POST']},
897 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
898 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
898
899
899 rmap.connect('pullrequest_repo_refs',
900 rmap.connect('pullrequest_repo_refs',
900 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
901 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
901 controller='pullrequests',
902 controller='pullrequests',
902 action='get_repo_refs',
903 action='get_repo_refs',
903 conditions={'function': check_repo, 'method': ['GET']},
904 conditions={'function': check_repo, 'method': ['GET']},
904 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
905
906
906 rmap.connect('pullrequest_repo_destinations',
907 rmap.connect('pullrequest_repo_destinations',
907 '/{repo_name}/pull-request/repo-destinations',
908 '/{repo_name}/pull-request/repo-destinations',
908 controller='pullrequests',
909 controller='pullrequests',
909 action='get_repo_destinations',
910 action='get_repo_destinations',
910 conditions={'function': check_repo, 'method': ['GET']},
911 conditions={'function': check_repo, 'method': ['GET']},
911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912
913
913 rmap.connect('pullrequest_show',
914 rmap.connect('pullrequest_show',
914 '/{repo_name}/pull-request/{pull_request_id}',
915 '/{repo_name}/pull-request/{pull_request_id}',
915 controller='pullrequests',
916 controller='pullrequests',
916 action='show', conditions={'function': check_repo,
917 action='show', conditions={'function': check_repo,
917 'method': ['GET']},
918 'method': ['GET']},
918 requirements=URL_NAME_REQUIREMENTS)
919 requirements=URL_NAME_REQUIREMENTS)
919
920
920 rmap.connect('pullrequest_update',
921 rmap.connect('pullrequest_update',
921 '/{repo_name}/pull-request/{pull_request_id}',
922 '/{repo_name}/pull-request/{pull_request_id}',
922 controller='pullrequests',
923 controller='pullrequests',
923 action='update', conditions={'function': check_repo,
924 action='update', conditions={'function': check_repo,
924 'method': ['PUT']},
925 'method': ['PUT']},
925 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
926 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
926
927
927 rmap.connect('pullrequest_merge',
928 rmap.connect('pullrequest_merge',
928 '/{repo_name}/pull-request/{pull_request_id}',
929 '/{repo_name}/pull-request/{pull_request_id}',
929 controller='pullrequests',
930 controller='pullrequests',
930 action='merge', conditions={'function': check_repo,
931 action='merge', conditions={'function': check_repo,
931 'method': ['POST']},
932 'method': ['POST']},
932 requirements=URL_NAME_REQUIREMENTS)
933 requirements=URL_NAME_REQUIREMENTS)
933
934
934 rmap.connect('pullrequest_delete',
935 rmap.connect('pullrequest_delete',
935 '/{repo_name}/pull-request/{pull_request_id}',
936 '/{repo_name}/pull-request/{pull_request_id}',
936 controller='pullrequests',
937 controller='pullrequests',
937 action='delete', conditions={'function': check_repo,
938 action='delete', conditions={'function': check_repo,
938 'method': ['DELETE']},
939 'method': ['DELETE']},
939 requirements=URL_NAME_REQUIREMENTS)
940 requirements=URL_NAME_REQUIREMENTS)
940
941
941 rmap.connect('pullrequest_show_all',
942 rmap.connect('pullrequest_show_all',
942 '/{repo_name}/pull-request',
943 '/{repo_name}/pull-request',
943 controller='pullrequests',
944 controller='pullrequests',
944 action='show_all', conditions={'function': check_repo,
945 action='show_all', conditions={'function': check_repo,
945 'method': ['GET']},
946 'method': ['GET']},
946 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
947 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
947
948
948 rmap.connect('pullrequest_comment',
949 rmap.connect('pullrequest_comment',
949 '/{repo_name}/pull-request-comment/{pull_request_id}',
950 '/{repo_name}/pull-request-comment/{pull_request_id}',
950 controller='pullrequests',
951 controller='pullrequests',
951 action='comment', conditions={'function': check_repo,
952 action='comment', conditions={'function': check_repo,
952 'method': ['POST']},
953 'method': ['POST']},
953 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
954
955
955 rmap.connect('pullrequest_comment_delete',
956 rmap.connect('pullrequest_comment_delete',
956 '/{repo_name}/pull-request-comment/{comment_id}/delete',
957 '/{repo_name}/pull-request-comment/{comment_id}/delete',
957 controller='pullrequests', action='delete_comment',
958 controller='pullrequests', action='delete_comment',
958 conditions={'function': check_repo, 'method': ['DELETE']},
959 conditions={'function': check_repo, 'method': ['DELETE']},
959 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
960 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
960
961
961 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
962 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
962 controller='summary', conditions={'function': check_repo},
963 controller='summary', conditions={'function': check_repo},
963 requirements=URL_NAME_REQUIREMENTS)
964 requirements=URL_NAME_REQUIREMENTS)
964
965
965 rmap.connect('branches_home', '/{repo_name}/branches',
966 rmap.connect('branches_home', '/{repo_name}/branches',
966 controller='branches', conditions={'function': check_repo},
967 controller='branches', conditions={'function': check_repo},
967 requirements=URL_NAME_REQUIREMENTS)
968 requirements=URL_NAME_REQUIREMENTS)
968
969
969 rmap.connect('tags_home', '/{repo_name}/tags',
970 rmap.connect('tags_home', '/{repo_name}/tags',
970 controller='tags', conditions={'function': check_repo},
971 controller='tags', conditions={'function': check_repo},
971 requirements=URL_NAME_REQUIREMENTS)
972 requirements=URL_NAME_REQUIREMENTS)
972
973
973 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
974 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
974 controller='bookmarks', conditions={'function': check_repo},
975 controller='bookmarks', conditions={'function': check_repo},
975 requirements=URL_NAME_REQUIREMENTS)
976 requirements=URL_NAME_REQUIREMENTS)
976
977
977 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
978 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
978 controller='changelog', conditions={'function': check_repo},
979 controller='changelog', conditions={'function': check_repo},
979 requirements=URL_NAME_REQUIREMENTS)
980 requirements=URL_NAME_REQUIREMENTS)
980
981
981 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
982 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
982 controller='changelog', action='changelog_summary',
983 controller='changelog', action='changelog_summary',
983 conditions={'function': check_repo},
984 conditions={'function': check_repo},
984 requirements=URL_NAME_REQUIREMENTS)
985 requirements=URL_NAME_REQUIREMENTS)
985
986
986 rmap.connect('changelog_file_home',
987 rmap.connect('changelog_file_home',
987 '/{repo_name}/changelog/{revision}/{f_path}',
988 '/{repo_name}/changelog/{revision}/{f_path}',
988 controller='changelog', f_path=None,
989 controller='changelog', f_path=None,
989 conditions={'function': check_repo},
990 conditions={'function': check_repo},
990 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
991 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
991
992
992 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
993 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
993 controller='changelog', action='changelog_details',
994 controller='changelog', action='changelog_details',
994 conditions={'function': check_repo},
995 conditions={'function': check_repo},
995 requirements=URL_NAME_REQUIREMENTS)
996 requirements=URL_NAME_REQUIREMENTS)
996
997
997 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
998 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
998 controller='files', revision='tip', f_path='',
999 controller='files', revision='tip', f_path='',
999 conditions={'function': check_repo},
1000 conditions={'function': check_repo},
1000 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1001 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1001
1002
1002 rmap.connect('files_home_simple_catchrev',
1003 rmap.connect('files_home_simple_catchrev',
1003 '/{repo_name}/files/{revision}',
1004 '/{repo_name}/files/{revision}',
1004 controller='files', revision='tip', f_path='',
1005 controller='files', revision='tip', f_path='',
1005 conditions={'function': check_repo},
1006 conditions={'function': check_repo},
1006 requirements=URL_NAME_REQUIREMENTS)
1007 requirements=URL_NAME_REQUIREMENTS)
1007
1008
1008 rmap.connect('files_home_simple_catchall',
1009 rmap.connect('files_home_simple_catchall',
1009 '/{repo_name}/files',
1010 '/{repo_name}/files',
1010 controller='files', revision='tip', f_path='',
1011 controller='files', revision='tip', f_path='',
1011 conditions={'function': check_repo},
1012 conditions={'function': check_repo},
1012 requirements=URL_NAME_REQUIREMENTS)
1013 requirements=URL_NAME_REQUIREMENTS)
1013
1014
1014 rmap.connect('files_history_home',
1015 rmap.connect('files_history_home',
1015 '/{repo_name}/history/{revision}/{f_path}',
1016 '/{repo_name}/history/{revision}/{f_path}',
1016 controller='files', action='history', revision='tip', f_path='',
1017 controller='files', action='history', revision='tip', f_path='',
1017 conditions={'function': check_repo},
1018 conditions={'function': check_repo},
1018 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1019 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1019
1020
1020 rmap.connect('files_authors_home',
1021 rmap.connect('files_authors_home',
1021 '/{repo_name}/authors/{revision}/{f_path}',
1022 '/{repo_name}/authors/{revision}/{f_path}',
1022 controller='files', action='authors', revision='tip', f_path='',
1023 controller='files', action='authors', revision='tip', f_path='',
1023 conditions={'function': check_repo},
1024 conditions={'function': check_repo},
1024 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1025 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1025
1026
1026 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1027 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1027 controller='files', action='diff', f_path='',
1028 controller='files', action='diff', f_path='',
1028 conditions={'function': check_repo},
1029 conditions={'function': check_repo},
1029 requirements=URL_NAME_REQUIREMENTS)
1030 requirements=URL_NAME_REQUIREMENTS)
1030
1031
1031 rmap.connect('files_diff_2way_home',
1032 rmap.connect('files_diff_2way_home',
1032 '/{repo_name}/diff-2way/{f_path}',
1033 '/{repo_name}/diff-2way/{f_path}',
1033 controller='files', action='diff_2way', f_path='',
1034 controller='files', action='diff_2way', f_path='',
1034 conditions={'function': check_repo},
1035 conditions={'function': check_repo},
1035 requirements=URL_NAME_REQUIREMENTS)
1036 requirements=URL_NAME_REQUIREMENTS)
1036
1037
1037 rmap.connect('files_rawfile_home',
1038 rmap.connect('files_rawfile_home',
1038 '/{repo_name}/rawfile/{revision}/{f_path}',
1039 '/{repo_name}/rawfile/{revision}/{f_path}',
1039 controller='files', action='rawfile', revision='tip',
1040 controller='files', action='rawfile', revision='tip',
1040 f_path='', conditions={'function': check_repo},
1041 f_path='', conditions={'function': check_repo},
1041 requirements=URL_NAME_REQUIREMENTS)
1042 requirements=URL_NAME_REQUIREMENTS)
1042
1043
1043 rmap.connect('files_raw_home',
1044 rmap.connect('files_raw_home',
1044 '/{repo_name}/raw/{revision}/{f_path}',
1045 '/{repo_name}/raw/{revision}/{f_path}',
1045 controller='files', action='raw', revision='tip', f_path='',
1046 controller='files', action='raw', revision='tip', f_path='',
1046 conditions={'function': check_repo},
1047 conditions={'function': check_repo},
1047 requirements=URL_NAME_REQUIREMENTS)
1048 requirements=URL_NAME_REQUIREMENTS)
1048
1049
1049 rmap.connect('files_render_home',
1050 rmap.connect('files_render_home',
1050 '/{repo_name}/render/{revision}/{f_path}',
1051 '/{repo_name}/render/{revision}/{f_path}',
1051 controller='files', action='index', revision='tip', f_path='',
1052 controller='files', action='index', revision='tip', f_path='',
1052 rendered=True, conditions={'function': check_repo},
1053 rendered=True, conditions={'function': check_repo},
1053 requirements=URL_NAME_REQUIREMENTS)
1054 requirements=URL_NAME_REQUIREMENTS)
1054
1055
1055 rmap.connect('files_annotate_home',
1056 rmap.connect('files_annotate_home',
1056 '/{repo_name}/annotate/{revision}/{f_path}',
1057 '/{repo_name}/annotate/{revision}/{f_path}',
1057 controller='files', action='index', revision='tip',
1058 controller='files', action='index', revision='tip',
1058 f_path='', annotate=True, conditions={'function': check_repo},
1059 f_path='', annotate=True, conditions={'function': check_repo},
1059 requirements=URL_NAME_REQUIREMENTS)
1060 requirements=URL_NAME_REQUIREMENTS)
1060
1061
1061 rmap.connect('files_edit',
1062 rmap.connect('files_edit',
1062 '/{repo_name}/edit/{revision}/{f_path}',
1063 '/{repo_name}/edit/{revision}/{f_path}',
1063 controller='files', action='edit', revision='tip',
1064 controller='files', action='edit', revision='tip',
1064 f_path='',
1065 f_path='',
1065 conditions={'function': check_repo, 'method': ['POST']},
1066 conditions={'function': check_repo, 'method': ['POST']},
1066 requirements=URL_NAME_REQUIREMENTS)
1067 requirements=URL_NAME_REQUIREMENTS)
1067
1068
1068 rmap.connect('files_edit_home',
1069 rmap.connect('files_edit_home',
1069 '/{repo_name}/edit/{revision}/{f_path}',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1070 controller='files', action='edit_home', revision='tip',
1071 controller='files', action='edit_home', revision='tip',
1071 f_path='', conditions={'function': check_repo},
1072 f_path='', conditions={'function': check_repo},
1072 requirements=URL_NAME_REQUIREMENTS)
1073 requirements=URL_NAME_REQUIREMENTS)
1073
1074
1074 rmap.connect('files_add',
1075 rmap.connect('files_add',
1075 '/{repo_name}/add/{revision}/{f_path}',
1076 '/{repo_name}/add/{revision}/{f_path}',
1076 controller='files', action='add', revision='tip',
1077 controller='files', action='add', revision='tip',
1077 f_path='',
1078 f_path='',
1078 conditions={'function': check_repo, 'method': ['POST']},
1079 conditions={'function': check_repo, 'method': ['POST']},
1079 requirements=URL_NAME_REQUIREMENTS)
1080 requirements=URL_NAME_REQUIREMENTS)
1080
1081
1081 rmap.connect('files_add_home',
1082 rmap.connect('files_add_home',
1082 '/{repo_name}/add/{revision}/{f_path}',
1083 '/{repo_name}/add/{revision}/{f_path}',
1083 controller='files', action='add_home', revision='tip',
1084 controller='files', action='add_home', revision='tip',
1084 f_path='', conditions={'function': check_repo},
1085 f_path='', conditions={'function': check_repo},
1085 requirements=URL_NAME_REQUIREMENTS)
1086 requirements=URL_NAME_REQUIREMENTS)
1086
1087
1087 rmap.connect('files_delete',
1088 rmap.connect('files_delete',
1088 '/{repo_name}/delete/{revision}/{f_path}',
1089 '/{repo_name}/delete/{revision}/{f_path}',
1089 controller='files', action='delete', revision='tip',
1090 controller='files', action='delete', revision='tip',
1090 f_path='',
1091 f_path='',
1091 conditions={'function': check_repo, 'method': ['POST']},
1092 conditions={'function': check_repo, 'method': ['POST']},
1092 requirements=URL_NAME_REQUIREMENTS)
1093 requirements=URL_NAME_REQUIREMENTS)
1093
1094
1094 rmap.connect('files_delete_home',
1095 rmap.connect('files_delete_home',
1095 '/{repo_name}/delete/{revision}/{f_path}',
1096 '/{repo_name}/delete/{revision}/{f_path}',
1096 controller='files', action='delete_home', revision='tip',
1097 controller='files', action='delete_home', revision='tip',
1097 f_path='', conditions={'function': check_repo},
1098 f_path='', conditions={'function': check_repo},
1098 requirements=URL_NAME_REQUIREMENTS)
1099 requirements=URL_NAME_REQUIREMENTS)
1099
1100
1100 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1101 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1101 controller='files', action='archivefile',
1102 controller='files', action='archivefile',
1102 conditions={'function': check_repo},
1103 conditions={'function': check_repo},
1103 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1104
1105
1105 rmap.connect('files_nodelist_home',
1106 rmap.connect('files_nodelist_home',
1106 '/{repo_name}/nodelist/{revision}/{f_path}',
1107 '/{repo_name}/nodelist/{revision}/{f_path}',
1107 controller='files', action='nodelist',
1108 controller='files', action='nodelist',
1108 conditions={'function': check_repo},
1109 conditions={'function': check_repo},
1109 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1110
1111
1111 rmap.connect('files_nodetree_full',
1112 rmap.connect('files_nodetree_full',
1112 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1113 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1113 controller='files', action='nodetree_full',
1114 controller='files', action='nodetree_full',
1114 conditions={'function': check_repo},
1115 conditions={'function': check_repo},
1115 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1116 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1116
1117
1117 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1118 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1118 controller='forks', action='fork_create',
1119 controller='forks', action='fork_create',
1119 conditions={'function': check_repo, 'method': ['POST']},
1120 conditions={'function': check_repo, 'method': ['POST']},
1120 requirements=URL_NAME_REQUIREMENTS)
1121 requirements=URL_NAME_REQUIREMENTS)
1121
1122
1122 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1123 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1123 controller='forks', action='fork',
1124 controller='forks', action='fork',
1124 conditions={'function': check_repo},
1125 conditions={'function': check_repo},
1125 requirements=URL_NAME_REQUIREMENTS)
1126 requirements=URL_NAME_REQUIREMENTS)
1126
1127
1127 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1128 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1128 controller='forks', action='forks',
1129 controller='forks', action='forks',
1129 conditions={'function': check_repo},
1130 conditions={'function': check_repo},
1130 requirements=URL_NAME_REQUIREMENTS)
1131 requirements=URL_NAME_REQUIREMENTS)
1131
1132
1132 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1133 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1133 controller='followers', action='followers',
1134 controller='followers', action='followers',
1134 conditions={'function': check_repo},
1135 conditions={'function': check_repo},
1135 requirements=URL_NAME_REQUIREMENTS)
1136 requirements=URL_NAME_REQUIREMENTS)
1136
1137
1137 # must be here for proper group/repo catching pattern
1138 # must be here for proper group/repo catching pattern
1138 _connect_with_slash(
1139 _connect_with_slash(
1139 rmap, 'repo_group_home', '/{group_name}',
1140 rmap, 'repo_group_home', '/{group_name}',
1140 controller='home', action='index_repo_group',
1141 controller='home', action='index_repo_group',
1141 conditions={'function': check_group},
1142 conditions={'function': check_group},
1142 requirements=URL_NAME_REQUIREMENTS)
1143 requirements=URL_NAME_REQUIREMENTS)
1143
1144
1144 # catch all, at the end
1145 # catch all, at the end
1145 _connect_with_slash(
1146 _connect_with_slash(
1146 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1147 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1147 controller='summary', action='index',
1148 controller='summary', action='index',
1148 conditions={'function': check_repo},
1149 conditions={'function': check_repo},
1149 requirements=URL_NAME_REQUIREMENTS)
1150 requirements=URL_NAME_REQUIREMENTS)
1150
1151
1151 return rmap
1152 return rmap
1152
1153
1153
1154
1154 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1155 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1155 """
1156 """
1156 Connect a route with an optional trailing slash in `path`.
1157 Connect a route with an optional trailing slash in `path`.
1157 """
1158 """
1158 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1159 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1159 mapper.connect(name, path, *args, **kwargs)
1160 mapper.connect(name, path, *args, **kwargs)
@@ -1,200 +1,238 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.model.db import Repository, Integration, RepoGroup
23 from rhodecode.model.db import Repository, Integration, RepoGroup
24 from rhodecode.config.routing import (
24 from rhodecode.config.routing import (
25 ADMIN_PREFIX, add_route_requirements, URL_NAME_REQUIREMENTS)
25 ADMIN_PREFIX, add_route_requirements, URL_NAME_REQUIREMENTS)
26 from rhodecode.integrations import integration_type_registry
26 from rhodecode.integrations import integration_type_registry
27
27
28 log = logging.getLogger(__name__)
28 log = logging.getLogger(__name__)
29
29
30
30
31 def includeme(config):
31 def includeme(config):
32
32
33 # global integrations
33 # global integrations
34
35 config.add_route('global_integrations_new',
36 ADMIN_PREFIX + '/integrations/new')
37 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
38 attr='new_integration',
39 renderer='rhodecode:templates/admin/integrations/new.html',
40 request_method='GET',
41 route_name='global_integrations_new')
42
34 config.add_route('global_integrations_home',
43 config.add_route('global_integrations_home',
35 ADMIN_PREFIX + '/integrations')
44 ADMIN_PREFIX + '/integrations')
36 config.add_route('global_integrations_list',
45 config.add_route('global_integrations_list',
37 ADMIN_PREFIX + '/integrations/{integration}')
46 ADMIN_PREFIX + '/integrations/{integration}')
38 for route_name in ['global_integrations_home', 'global_integrations_list']:
47 for route_name in ['global_integrations_home', 'global_integrations_list']:
39 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
48 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
40 attr='index',
49 attr='index',
41 renderer='rhodecode:templates/admin/integrations/list.html',
50 renderer='rhodecode:templates/admin/integrations/list.html',
42 request_method='GET',
51 request_method='GET',
43 route_name=route_name)
52 route_name=route_name)
44
53
45 config.add_route('global_integrations_create',
54 config.add_route('global_integrations_create',
46 ADMIN_PREFIX + '/integrations/{integration}/new',
55 ADMIN_PREFIX + '/integrations/{integration}/new',
47 custom_predicates=(valid_integration,))
56 custom_predicates=(valid_integration,))
48 config.add_route('global_integrations_edit',
57 config.add_route('global_integrations_edit',
49 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
58 ADMIN_PREFIX + '/integrations/{integration}/{integration_id}',
50 custom_predicates=(valid_integration,))
59 custom_predicates=(valid_integration,))
60
61
51 for route_name in ['global_integrations_create', 'global_integrations_edit']:
62 for route_name in ['global_integrations_create', 'global_integrations_edit']:
52 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
63 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
53 attr='settings_get',
64 attr='settings_get',
54 renderer='rhodecode:templates/admin/integrations/edit.html',
65 renderer='rhodecode:templates/admin/integrations/form.html',
55 request_method='GET',
66 request_method='GET',
56 route_name=route_name)
67 route_name=route_name)
57 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
68 config.add_view('rhodecode.integrations.views.GlobalIntegrationsView',
58 attr='settings_post',
69 attr='settings_post',
59 renderer='rhodecode:templates/admin/integrations/edit.html',
70 renderer='rhodecode:templates/admin/integrations/form.html',
71 request_method='POST',
72 route_name=route_name)
73
74
75 # repo group integrations
76 config.add_route('repo_group_integrations_home',
77 add_route_requirements(
78 '{repo_group_name}/settings/integrations',
79 URL_NAME_REQUIREMENTS
80 ),
81 custom_predicates=(valid_repo_group,)
82 )
83 config.add_route('repo_group_integrations_list',
84 add_route_requirements(
85 '{repo_group_name}/settings/integrations/{integration}',
86 URL_NAME_REQUIREMENTS
87 ),
88 custom_predicates=(valid_repo_group, valid_integration))
89 for route_name in ['repo_group_integrations_home', 'repo_group_integrations_list']:
90 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
91 attr='index',
92 renderer='rhodecode:templates/admin/integrations/list.html',
93 request_method='GET',
94 route_name=route_name)
95
96 config.add_route('repo_group_integrations_new',
97 add_route_requirements(
98 '{repo_group_name}/settings/integrations/new',
99 URL_NAME_REQUIREMENTS
100 ),
101 custom_predicates=(valid_repo_group,))
102 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
103 attr='new_integration',
104 renderer='rhodecode:templates/admin/integrations/new.html',
105 request_method='GET',
106 route_name='repo_group_integrations_new')
107
108 config.add_route('repo_group_integrations_create',
109 add_route_requirements(
110 '{repo_group_name}/settings/integrations/{integration}/new',
111 URL_NAME_REQUIREMENTS
112 ),
113 custom_predicates=(valid_repo_group, valid_integration))
114 config.add_route('repo_group_integrations_edit',
115 add_route_requirements(
116 '{repo_group_name}/settings/integrations/{integration}/{integration_id}',
117 URL_NAME_REQUIREMENTS
118 ),
119 custom_predicates=(valid_repo_group, valid_integration))
120 for route_name in ['repo_group_integrations_edit', 'repo_group_integrations_create']:
121 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
122 attr='settings_get',
123 renderer='rhodecode:templates/admin/integrations/form.html',
124 request_method='GET',
125 route_name=route_name)
126 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
127 attr='settings_post',
128 renderer='rhodecode:templates/admin/integrations/form.html',
60 request_method='POST',
129 request_method='POST',
61 route_name=route_name)
130 route_name=route_name)
62
131
63
132
64 # repo integrations
133 # repo integrations
65 config.add_route('repo_integrations_home',
134 config.add_route('repo_integrations_home',
66 add_route_requirements(
135 add_route_requirements(
67 '{repo_name}/settings/integrations',
136 '{repo_name}/settings/integrations',
68 URL_NAME_REQUIREMENTS
137 URL_NAME_REQUIREMENTS
69 ),
138 ),
70 custom_predicates=(valid_repo,))
139 custom_predicates=(valid_repo,))
71 config.add_route('repo_integrations_list',
140 config.add_route('repo_integrations_list',
72 add_route_requirements(
141 add_route_requirements(
73 '{repo_name}/settings/integrations/{integration}',
142 '{repo_name}/settings/integrations/{integration}',
74 URL_NAME_REQUIREMENTS
143 URL_NAME_REQUIREMENTS
75 ),
144 ),
76 custom_predicates=(valid_repo, valid_integration))
145 custom_predicates=(valid_repo, valid_integration))
77 for route_name in ['repo_integrations_home', 'repo_integrations_list']:
146 for route_name in ['repo_integrations_home', 'repo_integrations_list']:
78 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
147 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
79 attr='index',
148 attr='index',
80 request_method='GET',
149 request_method='GET',
150 renderer='rhodecode:templates/admin/integrations/list.html',
81 route_name=route_name)
151 route_name=route_name)
82
152
153 config.add_route('repo_integrations_new',
154 add_route_requirements(
155 '{repo_name}/settings/integrations/new',
156 URL_NAME_REQUIREMENTS
157 ),
158 custom_predicates=(valid_repo,))
159 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
160 attr='new_integration',
161 renderer='rhodecode:templates/admin/integrations/new.html',
162 request_method='GET',
163 route_name='repo_integrations_new')
164
83 config.add_route('repo_integrations_create',
165 config.add_route('repo_integrations_create',
84 add_route_requirements(
166 add_route_requirements(
85 '{repo_name}/settings/integrations/{integration}/new',
167 '{repo_name}/settings/integrations/{integration}/new',
86 URL_NAME_REQUIREMENTS
168 URL_NAME_REQUIREMENTS
87 ),
169 ),
88 custom_predicates=(valid_repo, valid_integration))
170 custom_predicates=(valid_repo, valid_integration))
89 config.add_route('repo_integrations_edit',
171 config.add_route('repo_integrations_edit',
90 add_route_requirements(
172 add_route_requirements(
91 '{repo_name}/settings/integrations/{integration}/{integration_id}',
173 '{repo_name}/settings/integrations/{integration}/{integration_id}',
92 URL_NAME_REQUIREMENTS
174 URL_NAME_REQUIREMENTS
93 ),
175 ),
94 custom_predicates=(valid_repo, valid_integration))
176 custom_predicates=(valid_repo, valid_integration))
95 for route_name in ['repo_integrations_edit', 'repo_integrations_create']:
177 for route_name in ['repo_integrations_edit', 'repo_integrations_create']:
96 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
178 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
97 attr='settings_get',
179 attr='settings_get',
98 renderer='rhodecode:templates/admin/integrations/edit.html',
180 renderer='rhodecode:templates/admin/integrations/form.html',
99 request_method='GET',
181 request_method='GET',
100 route_name=route_name)
182 route_name=route_name)
101 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
183 config.add_view('rhodecode.integrations.views.RepoIntegrationsView',
102 attr='settings_post',
184 attr='settings_post',
103 renderer='rhodecode:templates/admin/integrations/edit.html',
185 renderer='rhodecode:templates/admin/integrations/form.html',
104 request_method='POST',
105 route_name=route_name)
106
107
108 # repo group integrations
109 config.add_route('repo_group_integrations_home',
110 add_route_requirements(
111 '{repo_group_name}/settings/integrations',
112 URL_NAME_REQUIREMENTS
113 ),
114 custom_predicates=(valid_repo_group,))
115 config.add_route('repo_group_integrations_list',
116 add_route_requirements(
117 '{repo_group_name}/settings/integrations/{integration}',
118 URL_NAME_REQUIREMENTS
119 ),
120 custom_predicates=(valid_repo_group, valid_integration))
121 for route_name in ['repo_group_integrations_home', 'repo_group_integrations_list']:
122 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
123 attr='index',
124 request_method='GET',
125 route_name=route_name)
126
127 config.add_route('repo_group_integrations_create',
128 add_route_requirements(
129 '{repo_group_name}/settings/integrations/{integration}/new',
130 URL_NAME_REQUIREMENTS
131 ),
132 custom_predicates=(valid_repo_group, valid_integration))
133 config.add_route('repo_group_integrations_edit',
134 add_route_requirements(
135 '{repo_group_name}/settings/integrations/{integration}/{integration_id}',
136 URL_NAME_REQUIREMENTS
137 ),
138 custom_predicates=(valid_repo_group, valid_integration))
139 for route_name in ['repo_group_integrations_edit', 'repo_group_integrations_create']:
140 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
141 attr='settings_get',
142 renderer='rhodecode:templates/admin/integrations/edit.html',
143 request_method='GET',
144 route_name=route_name)
145 config.add_view('rhodecode.integrations.views.RepoGroupIntegrationsView',
146 attr='settings_post',
147 renderer='rhodecode:templates/admin/integrations/edit.html',
148 request_method='POST',
186 request_method='POST',
149 route_name=route_name)
187 route_name=route_name)
150
188
151
189
152 def valid_repo(info, request):
190 def valid_repo(info, request):
153 repo = Repository.get_by_repo_name(info['match']['repo_name'])
191 repo = Repository.get_by_repo_name(info['match']['repo_name'])
154 if repo:
192 if repo:
155 return True
193 return True
156
194
157
195
158 def valid_repo_group(info, request):
196 def valid_repo_group(info, request):
159 repo_group = RepoGroup.get_by_group_name(info['match']['repo_group_name'])
197 repo_group = RepoGroup.get_by_group_name(info['match']['repo_group_name'])
160 if repo_group:
198 if repo_group:
161 return True
199 return True
162 return False
200 return False
163
201
164
202
165 def valid_integration(info, request):
203 def valid_integration(info, request):
166 integration_type = info['match']['integration']
204 integration_type = info['match']['integration']
167 integration_id = info['match'].get('integration_id')
205 integration_id = info['match'].get('integration_id')
168 repo_name = info['match'].get('repo_name')
206 repo_name = info['match'].get('repo_name')
169 repo_group_name = info['match'].get('repo_group_name')
207 repo_group_name = info['match'].get('repo_group_name')
170
208
171 if integration_type not in integration_type_registry:
209 if integration_type not in integration_type_registry:
172 return False
210 return False
173
211
174 repo, repo_group = None, None
212 repo, repo_group = None, None
175 if repo_name:
213 if repo_name:
176 repo = Repository.get_by_repo_name(repo_name)
214 repo = Repository.get_by_repo_name(repo_name)
177 if not repo:
215 if not repo:
178 return False
216 return False
179
217
180 if repo_group_name:
218 if repo_group_name:
181 repo_group = RepoGroup.get_by_group_name(repo_group_name)
219 repo_group = RepoGroup.get_by_group_name(repo_group_name)
182 if not repo_group:
220 if not repo_group:
183 return False
221 return False
184
222
185 if repo_name and repo_group:
223 if repo_name and repo_group:
186 raise Exception('Either repo or repo_group can be set, not both')
224 raise Exception('Either repo or repo_group can be set, not both')
187
225
188
226
189 if integration_id:
227 if integration_id:
190 integration = Integration.get(integration_id)
228 integration = Integration.get(integration_id)
191 if not integration:
229 if not integration:
192 return False
230 return False
193 if integration.integration_type != integration_type:
231 if integration.integration_type != integration_type:
194 return False
232 return False
195 if repo and repo.repo_id != integration.repo_id:
233 if repo and repo.repo_id != integration.repo_id:
196 return False
234 return False
197 if repo_group and repo_group.repo_group_id != integration.repo_group_id:
235 if repo_group and repo_group.group_id != integration.repo_group_id:
198 return False
236 return False
199
237
200 return True
238 return True
@@ -1,45 +1,71 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
21 import colander
22
22
23 from rhodecode.translation import lazy_ugettext
23 from rhodecode.translation import _
24
24
25
25
26 class IntegrationSettingsSchemaBase(colander.MappingSchema):
26 class IntegrationOptionsSchemaBase(colander.MappingSchema):
27 """
28 This base schema is intended for use in integrations.
29 It adds a few default settings (e.g., "enabled"), so that integration
30 authors don't have to maintain a bunch of boilerplate.
31 """
32 enabled = colander.SchemaNode(
27 enabled = colander.SchemaNode(
33 colander.Bool(),
28 colander.Bool(),
34 default=True,
29 default=True,
35 description=lazy_ugettext('Enable or disable this integration.'),
30 description=_('Enable or disable this integration.'),
36 missing=False,
31 missing=False,
37 title=lazy_ugettext('Enabled'),
32 title=_('Enabled'),
38 )
33 )
39
34
40 name = colander.SchemaNode(
35 name = colander.SchemaNode(
41 colander.String(),
36 colander.String(),
42 description=lazy_ugettext('Short name for this integration.'),
37 description=_('Short name for this integration.'),
43 missing=colander.required,
38 missing=colander.required,
44 title=lazy_ugettext('Integration name'),
39 title=_('Integration name'),
45 )
40 )
41
42
43 class RepoIntegrationOptionsSchema(IntegrationOptionsSchemaBase):
44 pass
45
46
47 class RepoGroupIntegrationOptionsSchema(IntegrationOptionsSchemaBase):
48 child_repos_only = colander.SchemaNode(
49 colander.Bool(),
50 default=True,
51 description=_(
52 'Limit integrations to to work only on the direct children '
53 'repositories of this repository group (no subgroups)'),
54 missing=False,
55 title=_('Limit to childen repos only'),
56 )
57
58
59 class GlobalIntegrationOptionsSchema(IntegrationOptionsSchemaBase):
60 child_repos_only = colander.SchemaNode(
61 colander.Bool(),
62 default=False,
63 description=_(
64 'Limit integrations to to work only on root level repositories'),
65 missing=False,
66 title=_('Root repositories only'),
67 )
68
69
70 class IntegrationSettingsSchemaBase(colander.MappingSchema):
71 pass
@@ -1,42 +1,101 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
21 import colander
22 from rhodecode.translation import _
22
23
23
24
24 class IntegrationTypeBase(object):
25 class IntegrationTypeBase(object):
25 """ Base class for IntegrationType plugins """
26 """ Base class for IntegrationType plugins """
26
27
28 description = ''
29 icon = '''
30 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
31 <svg
32 xmlns:dc="http://purl.org/dc/elements/1.1/"
33 xmlns:cc="http://creativecommons.org/ns#"
34 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
35 xmlns:svg="http://www.w3.org/2000/svg"
36 xmlns="http://www.w3.org/2000/svg"
37 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
38 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
39 viewBox="0 -256 1792 1792"
40 id="svg3025"
41 version="1.1"
42 inkscape:version="0.48.3.1 r9886"
43 width="100%"
44 height="100%"
45 sodipodi:docname="cog_font_awesome.svg">
46 <metadata
47 id="metadata3035">
48 <rdf:RDF>
49 <cc:Work
50 rdf:about="">
51 <dc:format>image/svg+xml</dc:format>
52 <dc:type
53 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
54 </cc:Work>
55 </rdf:RDF>
56 </metadata>
57 <defs
58 id="defs3033" />
59 <sodipodi:namedview
60 pagecolor="#ffffff"
61 bordercolor="#666666"
62 borderopacity="1"
63 objecttolerance="10"
64 gridtolerance="10"
65 guidetolerance="10"
66 inkscape:pageopacity="0"
67 inkscape:pageshadow="2"
68 inkscape:window-width="640"
69 inkscape:window-height="480"
70 id="namedview3031"
71 showgrid="false"
72 inkscape:zoom="0.13169643"
73 inkscape:cx="896"
74 inkscape:cy="896"
75 inkscape:window-x="0"
76 inkscape:window-y="25"
77 inkscape:window-maximized="0"
78 inkscape:current-layer="svg3025" />
79 <g
80 transform="matrix(1,0,0,-1,121.49153,1285.4237)"
81 id="g3027">
82 <path
83 d="m 1024,640 q 0,106 -75,181 -75,75 -181,75 -106,0 -181,-75 -75,-75 -75,-181 0,-106 75,-181 75,-75 181,-75 106,0 181,75 75,75 75,181 z m 512,109 V 527 q 0,-12 -8,-23 -8,-11 -20,-13 l -185,-28 q -19,-54 -39,-91 35,-50 107,-138 10,-12 10,-25 0,-13 -9,-23 -27,-37 -99,-108 -72,-71 -94,-71 -12,0 -26,9 l -138,108 q -44,-23 -91,-38 -16,-136 -29,-186 -7,-28 -36,-28 H 657 q -14,0 -24.5,8.5 Q 622,-111 621,-98 L 593,86 q -49,16 -90,37 L 362,16 Q 352,7 337,7 323,7 312,18 186,132 147,186 q -7,10 -7,23 0,12 8,23 15,21 51,66.5 36,45.5 54,70.5 -27,50 -41,99 L 29,495 Q 16,497 8,507.5 0,518 0,531 v 222 q 0,12 8,23 8,11 19,13 l 186,28 q 14,46 39,92 -40,57 -107,138 -10,12 -10,24 0,10 9,23 26,36 98.5,107.5 72.5,71.5 94.5,71.5 13,0 26,-10 l 138,-107 q 44,23 91,38 16,136 29,186 7,28 36,28 h 222 q 14,0 24.5,-8.5 Q 914,1391 915,1378 l 28,-184 q 49,-16 90,-37 l 142,107 q 9,9 24,9 13,0 25,-10 129,-119 165,-170 7,-8 7,-22 0,-12 -8,-23 -15,-21 -51,-66.5 -36,-45.5 -54,-70.5 26,-50 41,-98 l 183,-28 q 13,-2 21,-12.5 8,-10.5 8,-23.5 z"
84 id="path3029"
85 inkscape:connector-curvature="0"
86 style="fill:currentColor" />
87 </g>
88 </svg>
89 '''
90
27 def __init__(self, settings):
91 def __init__(self, settings):
28 """
92 """
29 :param settings: dict of settings to be used for the integration
93 :param settings: dict of settings to be used for the integration
30 """
94 """
31 self.settings = settings
95 self.settings = settings
32
96
33
34 def settings_schema(self):
97 def settings_schema(self):
35 """
98 """
36 A colander schema of settings for the integration type
99 A colander schema of settings for the integration type
37
38 Subclasses can return their own schema but should always
39 inherit from IntegrationSettingsSchemaBase
40 """
100 """
41 return IntegrationSettingsSchemaBase()
101 return colander.Schema()
42
@@ -1,222 +1,283 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22 import deform
22 import deform
23 import logging
23 import logging
24 import colander
24 import colander
25
25
26 from mako.template import Template
26 from mako.template import Template
27
27
28 from rhodecode import events
28 from rhodecode import events
29 from rhodecode.translation import _, lazy_ugettext
29 from rhodecode.translation import _
30 from rhodecode.lib.celerylib import run_task
30 from rhodecode.lib.celerylib import run_task
31 from rhodecode.lib.celerylib import tasks
31 from rhodecode.lib.celerylib import tasks
32 from rhodecode.integrations.types.base import IntegrationTypeBase
32 from rhodecode.integrations.types.base import IntegrationTypeBase
33 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
34
33
35
34
36 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
37
36
38 repo_push_template_plaintext = Template('''
37 repo_push_template_plaintext = Template('''
39 Commits:
38 Commits:
40
39
41 % for commit in data['push']['commits']:
40 % for commit in data['push']['commits']:
42 ${commit['url']} by ${commit['author']} at ${commit['date']}
41 ${commit['url']} by ${commit['author']} at ${commit['date']}
43 ${commit['message']}
42 ${commit['message']}
44 ----
43 ----
45
44
46 % endfor
45 % endfor
47 ''')
46 ''')
48
47
49 ## TODO (marcink): think about putting this into a file, or use base.mako email template
48 ## TODO (marcink): think about putting this into a file, or use base.mako email template
50
49
51 repo_push_template_html = Template('''
50 repo_push_template_html = Template('''
52 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
51 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
53 <html xmlns="http://www.w3.org/1999/xhtml">
52 <html xmlns="http://www.w3.org/1999/xhtml">
54 <head>
53 <head>
55 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
54 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
56 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
55 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
57 <title>${subject}</title>
56 <title>${subject}</title>
58 <style type="text/css">
57 <style type="text/css">
59 /* Based on The MailChimp Reset INLINE: Yes. */
58 /* Based on The MailChimp Reset INLINE: Yes. */
60 #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
59 #outlook a {padding:0;} /* Force Outlook to provide a "view in browser" menu link. */
61 body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
60 body{width:100% !important; -webkit-text-size-adjust:100%; -ms-text-size-adjust:100%; margin:0; padding:0;}
62 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
61 /* Prevent Webkit and Windows Mobile platforms from changing default font sizes.*/
63 .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
62 .ExternalClass {width:100%;} /* Force Hotmail to display emails at full width */
64 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
63 .ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
65 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
64 /* Forces Hotmail to display normal line spacing. More on that: http://www.emailonacid.com/forum/viewthread/43/ */
66 #backgroundTable {margin:0; padding:0; line-height: 100% !important;}
65 #backgroundTable {margin:0; padding:0; line-height: 100% !important;}
67 /* End reset */
66 /* End reset */
68
67
69 /* defaults for images*/
68 /* defaults for images*/
70 img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
69 img {outline:none; text-decoration:none; -ms-interpolation-mode: bicubic;}
71 a img {border:none;}
70 a img {border:none;}
72 .image_fix {display:block;}
71 .image_fix {display:block;}
73
72
74 body {line-height:1.2em;}
73 body {line-height:1.2em;}
75 p {margin: 0 0 20px;}
74 p {margin: 0 0 20px;}
76 h1, h2, h3, h4, h5, h6 {color:#323232!important;}
75 h1, h2, h3, h4, h5, h6 {color:#323232!important;}
77 a {color:#427cc9;text-decoration:none;outline:none;cursor:pointer;}
76 a {color:#427cc9;text-decoration:none;outline:none;cursor:pointer;}
78 a:focus {outline:none;}
77 a:focus {outline:none;}
79 a:hover {color: #305b91;}
78 a:hover {color: #305b91;}
80 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color:#427cc9!important;text-decoration:none!important;}
79 h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {color:#427cc9!important;text-decoration:none!important;}
81 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: #305b91!important;}
80 h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: #305b91!important;}
82 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: #305b91!important;}
81 h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: #305b91!important;}
83 table {font-size:13px;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}
82 table {font-size:13px;border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt;}
84 table td {padding:.65em 1em .65em 0;border-collapse:collapse;vertical-align:top;text-align:left;}
83 table td {padding:.65em 1em .65em 0;border-collapse:collapse;vertical-align:top;text-align:left;}
85 input {display:inline;border-radius:2px;border-style:solid;border: 1px solid #dbd9da;padding:.5em;}
84 input {display:inline;border-radius:2px;border-style:solid;border: 1px solid #dbd9da;padding:.5em;}
86 input:focus {outline: 1px solid #979797}
85 input:focus {outline: 1px solid #979797}
87 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
86 @media only screen and (-webkit-min-device-pixel-ratio: 2) {
88 /* Put your iPhone 4g styles in here */
87 /* Put your iPhone 4g styles in here */
89 }
88 }
90
89
91 /* Android targeting */
90 /* Android targeting */
92 @media only screen and (-webkit-device-pixel-ratio:.75){
91 @media only screen and (-webkit-device-pixel-ratio:.75){
93 /* Put CSS for low density (ldpi) Android layouts in here */
92 /* Put CSS for low density (ldpi) Android layouts in here */
94 }
93 }
95 @media only screen and (-webkit-device-pixel-ratio:1){
94 @media only screen and (-webkit-device-pixel-ratio:1){
96 /* Put CSS for medium density (mdpi) Android layouts in here */
95 /* Put CSS for medium density (mdpi) Android layouts in here */
97 }
96 }
98 @media only screen and (-webkit-device-pixel-ratio:1.5){
97 @media only screen and (-webkit-device-pixel-ratio:1.5){
99 /* Put CSS for high density (hdpi) Android layouts in here */
98 /* Put CSS for high density (hdpi) Android layouts in here */
100 }
99 }
101 /* end Android targeting */
100 /* end Android targeting */
102
101
103 </style>
102 </style>
104
103
105 <!-- Targeting Windows Mobile -->
104 <!-- Targeting Windows Mobile -->
106 <!--[if IEMobile 7]>
105 <!--[if IEMobile 7]>
107 <style type="text/css">
106 <style type="text/css">
108
107
109 </style>
108 </style>
110 <![endif]-->
109 <![endif]-->
111
110
112 <!--[if gte mso 9]>
111 <!--[if gte mso 9]>
113 <style>
112 <style>
114 /* Target Outlook 2007 and 2010 */
113 /* Target Outlook 2007 and 2010 */
115 </style>
114 </style>
116 <![endif]-->
115 <![endif]-->
117 </head>
116 </head>
118 <body>
117 <body>
119 <!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
118 <!-- Wrapper/Container Table: Use a wrapper table to control the width and the background color consistently of your email. Use this approach instead of setting attributes on the body tag. -->
120 <table cellpadding="0" cellspacing="0" border="0" id="backgroundTable" align="left" style="margin:1%;width:97%;padding:0;font-family:sans-serif;font-weight:100;border:1px solid #dbd9da">
119 <table cellpadding="0" cellspacing="0" border="0" id="backgroundTable" align="left" style="margin:1%;width:97%;padding:0;font-family:sans-serif;font-weight:100;border:1px solid #dbd9da">
121 <tr>
120 <tr>
122 <td valign="top" style="padding:0;">
121 <td valign="top" style="padding:0;">
123 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
122 <table cellpadding="0" cellspacing="0" border="0" align="left" width="100%">
124 <tr><td style="width:100%;padding:7px;background-color:#202020" valign="top">
123 <tr><td style="width:100%;padding:7px;background-color:#202020" valign="top">
125 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
124 <a style="color:#eeeeee;text-decoration:none;" href="${instance_url}">
126 ${'RhodeCode'}
125 ${'RhodeCode'}
127 </a>
126 </a>
128 </td></tr>
127 </td></tr>
129 <tr>
128 <tr>
130 <td style="padding:15px;" valign="top">
129 <td style="padding:15px;" valign="top">
131 % for commit in data['push']['commits']:
130 % for commit in data['push']['commits']:
132 <a href="${commit['url']}">${commit['short_id']}</a> by ${commit['author']} at ${commit['date']} <br/>
131 <a href="${commit['url']}">${commit['short_id']}</a> by ${commit['author']} at ${commit['date']} <br/>
133 ${commit['message_html']} <br/>
132 ${commit['message_html']} <br/>
134 <br/>
133 <br/>
135 % endfor
134 % endfor
136 </td>
135 </td>
137 </tr>
136 </tr>
138 </table>
137 </table>
139 </td>
138 </td>
140 </tr>
139 </tr>
141 </table>
140 </table>
142 <!-- End of wrapper table -->
141 <!-- End of wrapper table -->
143 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;color:#666666;text-decoration:none;" href="${instance_url}">
142 <p><a style="margin-top:15px;margin-left:1%;font-family:sans-serif;font-weight:100;font-size:11px;color:#666666;text-decoration:none;" href="${instance_url}">
144 ${'This is a notification from RhodeCode. %(instance_url)s' % {'instance_url': instance_url}}
143 ${'This is a notification from RhodeCode. %(instance_url)s' % {'instance_url': instance_url}}
145 </a></p>
144 </a></p>
146 </body>
145 </body>
147 </html>
146 </html>
148 ''')
147 ''')
149
148
149 email_icon = '''
150 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
151 <svg
152 xmlns:dc="http://purl.org/dc/elements/1.1/"
153 xmlns:cc="http://creativecommons.org/ns#"
154 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
155 xmlns:svg="http://www.w3.org/2000/svg"
156 xmlns="http://www.w3.org/2000/svg"
157 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
158 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
159 viewBox="0 -256 1850 1850"
160 id="svg2989"
161 version="1.1"
162 inkscape:version="0.48.3.1 r9886"
163 width="100%"
164 height="100%"
165 sodipodi:docname="envelope_font_awesome.svg">
166 <metadata
167 id="metadata2999">
168 <rdf:RDF>
169 <cc:Work
170 rdf:about="">
171 <dc:format>image/svg+xml</dc:format>
172 <dc:type
173 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
174 </cc:Work>
175 </rdf:RDF>
176 </metadata>
177 <defs
178 id="defs2997" />
179 <sodipodi:namedview
180 pagecolor="#ffffff"
181 bordercolor="#666666"
182 borderopacity="1"
183 objecttolerance="10"
184 gridtolerance="10"
185 guidetolerance="10"
186 inkscape:pageopacity="0"
187 inkscape:pageshadow="2"
188 inkscape:window-width="640"
189 inkscape:window-height="480"
190 id="namedview2995"
191 showgrid="false"
192 inkscape:zoom="0.13169643"
193 inkscape:cx="896"
194 inkscape:cy="896"
195 inkscape:window-x="0"
196 inkscape:window-y="25"
197 inkscape:window-maximized="0"
198 inkscape:current-layer="svg2989" />
199 <g
200 transform="matrix(1,0,0,-1,37.966102,1282.678)"
201 id="g2991">
202 <path
203 d="m 1664,32 v 768 q -32,-36 -69,-66 -268,-206 -426,-338 -51,-43 -83,-67 -32,-24 -86.5,-48.5 Q 945,256 897,256 h -1 -1 Q 847,256 792.5,280.5 738,305 706,329 674,353 623,396 465,528 197,734 160,764 128,800 V 32 Q 128,19 137.5,9.5 147,0 160,0 h 1472 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,1051 v 11 13.5 q 0,0 -0.5,13 -0.5,13 -3,12.5 -2.5,-0.5 -5.5,9 -3,9.5 -9,7.5 -6,-2 -14,2.5 H 160 q -13,0 -22.5,-9.5 Q 128,1133 128,1120 128,952 275,836 468,684 676,519 682,514 711,489.5 740,465 757,452 774,439 801.5,420.5 829,402 852,393 q 23,-9 43,-9 h 1 1 q 20,0 43,9 23,9 50.5,27.5 27.5,18.5 44.5,31.5 17,13 46,37.5 29,24.5 35,29.5 208,165 401,317 54,43 100.5,115.5 46.5,72.5 46.5,131.5 z m 128,37 V 32 q 0,-66 -47,-113 -47,-47 -113,-47 H 160 Q 94,-128 47,-81 0,-34 0,32 v 1088 q 0,66 47,113 47,47 113,47 h 1472 q 66,0 113,-47 47,-47 47,-113 z"
204 id="path2993"
205 inkscape:connector-curvature="0"
206 style="fill:currentColor" />
207 </g>
208 </svg>
209 '''
150
210
151 class EmailSettingsSchema(IntegrationSettingsSchemaBase):
211 class EmailSettingsSchema(colander.Schema):
152 @colander.instantiate(validator=colander.Length(min=1))
212 @colander.instantiate(validator=colander.Length(min=1))
153 class recipients(colander.SequenceSchema):
213 class recipients(colander.SequenceSchema):
154 title = lazy_ugettext('Recipients')
214 title = _('Recipients')
155 description = lazy_ugettext('Email addresses to send push events to')
215 description = _('Email addresses to send push events to')
156 widget = deform.widget.SequenceWidget(min_len=1)
216 widget = deform.widget.SequenceWidget(min_len=1)
157
217
158 recipient = colander.SchemaNode(
218 recipient = colander.SchemaNode(
159 colander.String(),
219 colander.String(),
160 title=lazy_ugettext('Email address'),
220 title=_('Email address'),
161 description=lazy_ugettext('Email address'),
221 description=_('Email address'),
162 default='',
222 default='',
163 validator=colander.Email(),
223 validator=colander.Email(),
164 widget=deform.widget.TextInputWidget(
224 widget=deform.widget.TextInputWidget(
165 placeholder='user@domain.com',
225 placeholder='user@domain.com',
166 ),
226 ),
167 )
227 )
168
228
169
229
170 class EmailIntegrationType(IntegrationTypeBase):
230 class EmailIntegrationType(IntegrationTypeBase):
171 key = 'email'
231 key = 'email'
172 display_name = lazy_ugettext('Email')
232 display_name = _('Email')
173 SettingsSchema = EmailSettingsSchema
233 description = _('Send repo push summaries to a list of recipients via email')
234 icon = email_icon
174
235
175 def settings_schema(self):
236 def settings_schema(self):
176 schema = EmailSettingsSchema()
237 schema = EmailSettingsSchema()
177 return schema
238 return schema
178
239
179 def send_event(self, event):
240 def send_event(self, event):
180 data = event.as_dict()
241 data = event.as_dict()
181 log.debug('got event: %r', event)
242 log.debug('got event: %r', event)
182
243
183 if isinstance(event, events.RepoPushEvent):
244 if isinstance(event, events.RepoPushEvent):
184 repo_push_handler(data, self.settings)
245 repo_push_handler(data, self.settings)
185 else:
246 else:
186 log.debug('ignoring event: %r', event)
247 log.debug('ignoring event: %r', event)
187
248
188
249
189 def repo_push_handler(data, settings):
250 def repo_push_handler(data, settings):
190 commit_num = len(data['push']['commits'])
251 commit_num = len(data['push']['commits'])
191 server_url = data['server_url']
252 server_url = data['server_url']
192
253
193 if commit_num == 0:
254 if commit_num == 0:
194 subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}'.format(
255 subject = '[{repo_name}] {author} pushed {commit_num} commit on branches: {branches}'.format(
195 author=data['actor']['username'],
256 author=data['actor']['username'],
196 repo_name=data['repo']['repo_name'],
257 repo_name=data['repo']['repo_name'],
197 commit_num=commit_num,
258 commit_num=commit_num,
198 branches=', '.join(
259 branches=', '.join(
199 branch['name'] for branch in data['push']['branches'])
260 branch['name'] for branch in data['push']['branches'])
200 )
261 )
201 else:
262 else:
202 subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}'.format(
263 subject = '[{repo_name}] {author} pushed {commit_num} commits on branches: {branches}'.format(
203 author=data['actor']['username'],
264 author=data['actor']['username'],
204 repo_name=data['repo']['repo_name'],
265 repo_name=data['repo']['repo_name'],
205 commit_num=commit_num,
266 commit_num=commit_num,
206 branches=', '.join(
267 branches=', '.join(
207 branch['name'] for branch in data['push']['branches']))
268 branch['name'] for branch in data['push']['branches']))
208
269
209 email_body_plaintext = repo_push_template_plaintext.render(
270 email_body_plaintext = repo_push_template_plaintext.render(
210 data=data,
271 data=data,
211 subject=subject,
272 subject=subject,
212 instance_url=server_url)
273 instance_url=server_url)
213
274
214 email_body_html = repo_push_template_html.render(
275 email_body_html = repo_push_template_html.render(
215 data=data,
276 data=data,
216 subject=subject,
277 subject=subject,
217 instance_url=server_url)
278 instance_url=server_url)
218
279
219 for email_address in settings['recipients']:
280 for email_address in settings['recipients']:
220 run_task(
281 run_task(
221 tasks.send_email, email_address, subject,
282 tasks.send_email, email_address, subject,
222 email_body_plaintext, email_body_html)
283 email_body_plaintext, email_body_html)
@@ -1,242 +1,243 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22 import deform
22 import deform
23 import re
23 import re
24 import logging
24 import logging
25 import requests
25 import requests
26 import colander
26 import colander
27 import textwrap
27 import textwrap
28 from celery.task import task
28 from celery.task import task
29 from mako.template import Template
29 from mako.template import Template
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.translation import lazy_ugettext
32 from rhodecode.translation import _
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.celerylib import run_task
34 from rhodecode.lib.celerylib import run_task
35 from rhodecode.lib.colander_utils import strip_whitespace
35 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.integrations.types.base import IntegrationTypeBase
36 from rhodecode.integrations.types.base import IntegrationTypeBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
38
37
39 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
40
39
41
40
42 class HipchatSettingsSchema(IntegrationSettingsSchemaBase):
41 class HipchatSettingsSchema(colander.Schema):
43 color_choices = [
42 color_choices = [
44 ('yellow', lazy_ugettext('Yellow')),
43 ('yellow', _('Yellow')),
45 ('red', lazy_ugettext('Red')),
44 ('red', _('Red')),
46 ('green', lazy_ugettext('Green')),
45 ('green', _('Green')),
47 ('purple', lazy_ugettext('Purple')),
46 ('purple', _('Purple')),
48 ('gray', lazy_ugettext('Gray')),
47 ('gray', _('Gray')),
49 ]
48 ]
50
49
51 server_url = colander.SchemaNode(
50 server_url = colander.SchemaNode(
52 colander.String(),
51 colander.String(),
53 title=lazy_ugettext('Hipchat server URL'),
52 title=_('Hipchat server URL'),
54 description=lazy_ugettext('Hipchat integration url.'),
53 description=_('Hipchat integration url.'),
55 default='',
54 default='',
56 preparer=strip_whitespace,
55 preparer=strip_whitespace,
57 validator=colander.url,
56 validator=colander.url,
58 widget=deform.widget.TextInputWidget(
57 widget=deform.widget.TextInputWidget(
59 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
58 placeholder='https://?.hipchat.com/v2/room/?/notification?auth_token=?',
60 ),
59 ),
61 )
60 )
62 notify = colander.SchemaNode(
61 notify = colander.SchemaNode(
63 colander.Bool(),
62 colander.Bool(),
64 title=lazy_ugettext('Notify'),
63 title=_('Notify'),
65 description=lazy_ugettext('Make a notification to the users in room.'),
64 description=_('Make a notification to the users in room.'),
66 missing=False,
65 missing=False,
67 default=False,
66 default=False,
68 )
67 )
69 color = colander.SchemaNode(
68 color = colander.SchemaNode(
70 colander.String(),
69 colander.String(),
71 title=lazy_ugettext('Color'),
70 title=_('Color'),
72 description=lazy_ugettext('Background color of message.'),
71 description=_('Background color of message.'),
73 missing='',
72 missing='',
74 validator=colander.OneOf([x[0] for x in color_choices]),
73 validator=colander.OneOf([x[0] for x in color_choices]),
75 widget=deform.widget.Select2Widget(
74 widget=deform.widget.Select2Widget(
76 values=color_choices,
75 values=color_choices,
77 ),
76 ),
78 )
77 )
79
78
80
79
81 repo_push_template = Template('''
80 repo_push_template = Template('''
82 <b>${data['actor']['username']}</b> pushed to
81 <b>${data['actor']['username']}</b> pushed to
83 %if data['push']['branches']:
82 %if data['push']['branches']:
84 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'}
83 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'}
85 ${', '.join('<a href="%s">%s</a>' % (branch['url'], branch['name']) for branch in data['push']['branches'])}
84 ${', '.join('<a href="%s">%s</a>' % (branch['url'], branch['name']) for branch in data['push']['branches'])}
86 %else:
85 %else:
87 unknown branch
86 unknown branch
88 %endif
87 %endif
89 in <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>
88 in <a href="${data['repo']['url']}">${data['repo']['repo_name']}</a>
90 <br>
89 <br>
91 <ul>
90 <ul>
92 %for commit in data['push']['commits']:
91 %for commit in data['push']['commits']:
93 <li>
92 <li>
94 <a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}
93 <a href="${commit['url']}">${commit['short_id']}</a> - ${commit['message_html']}
95 </li>
94 </li>
96 %endfor
95 %endfor
97 </ul>
96 </ul>
98 ''')
97 ''')
99
98
100
99
101
102 class HipchatIntegrationType(IntegrationTypeBase):
100 class HipchatIntegrationType(IntegrationTypeBase):
103 key = 'hipchat'
101 key = 'hipchat'
104 display_name = lazy_ugettext('Hipchat')
102 display_name = _('Hipchat')
103 description = _('Send events such as repo pushes and pull requests to '
104 'your hipchat channel.')
105 icon = '''<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve"><g><g transform="translate(0.000000,511.000000) scale(0.100000,-0.100000)"><path fill="#205281" d="M4197.1,4662.4c-1661.5-260.4-3018-1171.6-3682.6-2473.3C219.9,1613.6,100,1120.3,100,462.6c0-1014,376.8-1918.4,1127-2699.4C2326.7-3377.6,3878.5-3898.3,5701-3730.5l486.5,44.5l208.9-123.3c637.2-373.4,1551.8-640.6,2240.4-650.9c304.9-6.9,335.7,0,417.9,75.4c185,174.7,147.3,411.1-89.1,548.1c-315.2,181.6-620,544.7-733.1,870.1l-51.4,157.6l472.7,472.7c349.4,349.4,520.7,551.5,657.7,774.2c784.5,1281.2,784.5,2788.5,0,4052.6c-236.4,376.8-794.8,966-1178.4,1236.7c-572.1,407.7-1264.1,709.1-1993.7,870.1c-267.2,58.2-479.6,75.4-1038,82.2C4714.4,4686.4,4310.2,4679.6,4197.1,4662.4z M5947.6,3740.9c1856.7-380.3,3127.6-1709.4,3127.6-3275c0-1000.3-534.4-1949.2-1466.2-2600.1c-188.4-133.6-287.8-226.1-301.5-284.4c-41.1-157.6,263.8-938.6,397.4-1020.8c20.5-10.3,34.3-44.5,34.3-75.4c0-167.8-811.9,195.3-1363.4,609.8l-181.6,137l-332.3-58.2c-445.3-78.8-1281.2-78.8-1702.6,0C2796-2569.2,1734.1-1832.6,1220.2-801.5C983.8-318.5,905,51.5,929,613.3c27.4,640.6,243.2,1192.1,685.1,1740.3c620,770.8,1661.5,1305.2,2822.8,1452.5C4806.9,3854,5553.7,3819.7,5947.6,3740.9z"/><path fill="#205281" d="M2381.5-345.9c-75.4-106.2-68.5-167.8,34.3-322c332.3-500.2,1010.6-928.4,1760.8-1120.2c417.9-106.2,1226.4-106.2,1644.3,0c712.5,181.6,1270.9,517.3,1685.4,1014C7681-561.7,7715.3-424.7,7616-325.4c-89.1,89.1-167.9,65.1-431.7-133.6c-835.8-630.3-2028-856.4-3086.5-585.8C3683.3-938.6,3142-685,2830.3-448.7C2576.8-253.4,2463.7-229.4,2381.5-345.9z"/></g></g><!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon --></svg>'''
105 valid_events = [
106 valid_events = [
106 events.PullRequestCloseEvent,
107 events.PullRequestCloseEvent,
107 events.PullRequestMergeEvent,
108 events.PullRequestMergeEvent,
108 events.PullRequestUpdateEvent,
109 events.PullRequestUpdateEvent,
109 events.PullRequestCommentEvent,
110 events.PullRequestCommentEvent,
110 events.PullRequestReviewEvent,
111 events.PullRequestReviewEvent,
111 events.PullRequestCreateEvent,
112 events.PullRequestCreateEvent,
112 events.RepoPushEvent,
113 events.RepoPushEvent,
113 events.RepoCreateEvent,
114 events.RepoCreateEvent,
114 ]
115 ]
115
116
116 def send_event(self, event):
117 def send_event(self, event):
117 if event.__class__ not in self.valid_events:
118 if event.__class__ not in self.valid_events:
118 log.debug('event not valid: %r' % event)
119 log.debug('event not valid: %r' % event)
119 return
120 return
120
121
121 if event.name not in self.settings['events']:
122 if event.name not in self.settings['events']:
122 log.debug('event ignored: %r' % event)
123 log.debug('event ignored: %r' % event)
123 return
124 return
124
125
125 data = event.as_dict()
126 data = event.as_dict()
126
127
127 text = '<b>%s<b> caused a <b>%s</b> event' % (
128 text = '<b>%s<b> caused a <b>%s</b> event' % (
128 data['actor']['username'], event.name)
129 data['actor']['username'], event.name)
129
130
130 log.debug('handling hipchat event for %s' % event.name)
131 log.debug('handling hipchat event for %s' % event.name)
131
132
132 if isinstance(event, events.PullRequestCommentEvent):
133 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
134 text = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
135 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
136 text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
137 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
138 text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
139 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
140 text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
141 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
142 text = self.format_repo_create_event(data)
142 else:
143 else:
143 log.error('unhandled event type: %r' % event)
144 log.error('unhandled event type: %r' % event)
144
145
145 run_task(post_text_to_hipchat, self.settings, text)
146 run_task(post_text_to_hipchat, self.settings, text)
146
147
147 def settings_schema(self):
148 def settings_schema(self):
148 schema = HipchatSettingsSchema()
149 schema = HipchatSettingsSchema()
149 schema.add(colander.SchemaNode(
150 schema.add(colander.SchemaNode(
150 colander.Set(),
151 colander.Set(),
151 widget=deform.widget.CheckboxChoiceWidget(
152 widget=deform.widget.CheckboxChoiceWidget(
152 values=sorted(
153 values=sorted(
153 [(e.name, e.display_name) for e in self.valid_events]
154 [(e.name, e.display_name) for e in self.valid_events]
154 )
155 )
155 ),
156 ),
156 description="Events activated for this integration",
157 description="Events activated for this integration",
157 name='events'
158 name='events'
158 ))
159 ))
159
160
160 return schema
161 return schema
161
162
162 def format_pull_request_comment_event(self, event, data):
163 def format_pull_request_comment_event(self, event, data):
163 comment_text = data['comment']['text']
164 comment_text = data['comment']['text']
164 if len(comment_text) > 200:
165 if len(comment_text) > 200:
165 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
166 comment_text = '{comment_text}<a href="{comment_url}">...<a/>'.format(
166 comment_text=comment_text[:200],
167 comment_text=comment_text[:200],
167 comment_url=data['comment']['url'],
168 comment_url=data['comment']['url'],
168 )
169 )
169
170
170 comment_status = ''
171 comment_status = ''
171 if data['comment']['status']:
172 if data['comment']['status']:
172 comment_status = '[{}]: '.format(data['comment']['status'])
173 comment_status = '[{}]: '.format(data['comment']['status'])
173
174
174 return (textwrap.dedent(
175 return (textwrap.dedent(
175 '''
176 '''
176 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
177 {user} commented on pull request <a href="{pr_url}">{number}</a> - {pr_title}:
177 >>> {comment_status}{comment_text}
178 >>> {comment_status}{comment_text}
178 ''').format(
179 ''').format(
179 comment_status=comment_status,
180 comment_status=comment_status,
180 user=data['actor']['username'],
181 user=data['actor']['username'],
181 number=data['pullrequest']['pull_request_id'],
182 number=data['pullrequest']['pull_request_id'],
182 pr_url=data['pullrequest']['url'],
183 pr_url=data['pullrequest']['url'],
183 pr_status=data['pullrequest']['status'],
184 pr_status=data['pullrequest']['status'],
184 pr_title=data['pullrequest']['title'],
185 pr_title=data['pullrequest']['title'],
185 comment_text=comment_text
186 comment_text=comment_text
186 )
187 )
187 )
188 )
188
189
189 def format_pull_request_review_event(self, event, data):
190 def format_pull_request_review_event(self, event, data):
190 return (textwrap.dedent(
191 return (textwrap.dedent(
191 '''
192 '''
192 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
193 Status changed to {pr_status} for pull request <a href="{pr_url}">#{number}</a> - {pr_title}
193 ''').format(
194 ''').format(
194 user=data['actor']['username'],
195 user=data['actor']['username'],
195 number=data['pullrequest']['pull_request_id'],
196 number=data['pullrequest']['pull_request_id'],
196 pr_url=data['pullrequest']['url'],
197 pr_url=data['pullrequest']['url'],
197 pr_status=data['pullrequest']['status'],
198 pr_status=data['pullrequest']['status'],
198 pr_title=data['pullrequest']['title'],
199 pr_title=data['pullrequest']['title'],
199 )
200 )
200 )
201 )
201
202
202 def format_pull_request_event(self, event, data):
203 def format_pull_request_event(self, event, data):
203 action = {
204 action = {
204 events.PullRequestCloseEvent: 'closed',
205 events.PullRequestCloseEvent: 'closed',
205 events.PullRequestMergeEvent: 'merged',
206 events.PullRequestMergeEvent: 'merged',
206 events.PullRequestUpdateEvent: 'updated',
207 events.PullRequestUpdateEvent: 'updated',
207 events.PullRequestCreateEvent: 'created',
208 events.PullRequestCreateEvent: 'created',
208 }.get(event.__class__, str(event.__class__))
209 }.get(event.__class__, str(event.__class__))
209
210
210 return ('Pull request <a href="{url}">#{number}</a> - {title} '
211 return ('Pull request <a href="{url}">#{number}</a> - {title} '
211 '{action} by {user}').format(
212 '{action} by {user}').format(
212 user=data['actor']['username'],
213 user=data['actor']['username'],
213 number=data['pullrequest']['pull_request_id'],
214 number=data['pullrequest']['pull_request_id'],
214 url=data['pullrequest']['url'],
215 url=data['pullrequest']['url'],
215 title=data['pullrequest']['title'],
216 title=data['pullrequest']['title'],
216 action=action
217 action=action
217 )
218 )
218
219
219 def format_repo_push_event(self, data):
220 def format_repo_push_event(self, data):
220 result = repo_push_template.render(
221 result = repo_push_template.render(
221 data=data,
222 data=data,
222 )
223 )
223 return result
224 return result
224
225
225 def format_repo_create_event(self, data):
226 def format_repo_create_event(self, data):
226 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
227 return '<a href="{}">{}</a> ({}) repository created by <b>{}</b>'.format(
227 data['repo']['url'],
228 data['repo']['url'],
228 data['repo']['repo_name'],
229 data['repo']['repo_name'],
229 data['repo']['repo_type'],
230 data['repo']['repo_type'],
230 data['actor']['username'],
231 data['actor']['username'],
231 )
232 )
232
233
233
234
234 @task(ignore_result=True)
235 @task(ignore_result=True)
235 def post_text_to_hipchat(settings, text):
236 def post_text_to_hipchat(settings, text):
236 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
237 log.debug('sending %s to hipchat %s' % (text, settings['server_url']))
237 resp = requests.post(settings['server_url'], json={
238 resp = requests.post(settings['server_url'], json={
238 "message": text,
239 "message": text,
239 "color": settings.get('color', 'yellow'),
240 "color": settings.get('color', 'yellow'),
240 "notify": settings.get('notify', False),
241 "notify": settings.get('notify', False),
241 })
242 })
242 resp.raise_for_status() # raise exception on a failed request
243 resp.raise_for_status() # raise exception on a failed request
@@ -1,253 +1,256 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22 import deform
22 import deform
23 import re
23 import re
24 import logging
24 import logging
25 import requests
25 import requests
26 import colander
26 import colander
27 import textwrap
27 import textwrap
28 from celery.task import task
28 from celery.task import task
29 from mako.template import Template
29 from mako.template import Template
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.translation import lazy_ugettext
32 from rhodecode.translation import _
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.celerylib import run_task
34 from rhodecode.lib.celerylib import run_task
35 from rhodecode.lib.colander_utils import strip_whitespace
35 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.integrations.types.base import IntegrationTypeBase
36 from rhodecode.integrations.types.base import IntegrationTypeBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
38
37
39 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
40
39
41
40
42 class SlackSettingsSchema(IntegrationSettingsSchemaBase):
41 class SlackSettingsSchema(colander.Schema):
43 service = colander.SchemaNode(
42 service = colander.SchemaNode(
44 colander.String(),
43 colander.String(),
45 title=lazy_ugettext('Slack service URL'),
44 title=_('Slack service URL'),
46 description=h.literal(lazy_ugettext(
45 description=h.literal(_(
47 'This can be setup at the '
46 'This can be setup at the '
48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
47 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 'slack app manager</a>')),
48 'slack app manager</a>')),
50 default='',
49 default='',
51 preparer=strip_whitespace,
50 preparer=strip_whitespace,
52 validator=colander.url,
51 validator=colander.url,
53 widget=deform.widget.TextInputWidget(
52 widget=deform.widget.TextInputWidget(
54 placeholder='https://hooks.slack.com/services/...',
53 placeholder='https://hooks.slack.com/services/...',
55 ),
54 ),
56 )
55 )
57 username = colander.SchemaNode(
56 username = colander.SchemaNode(
58 colander.String(),
57 colander.String(),
59 title=lazy_ugettext('Username'),
58 title=_('Username'),
60 description=lazy_ugettext('Username to show notifications coming from.'),
59 description=_('Username to show notifications coming from.'),
61 missing='Rhodecode',
60 missing='Rhodecode',
62 preparer=strip_whitespace,
61 preparer=strip_whitespace,
63 widget=deform.widget.TextInputWidget(
62 widget=deform.widget.TextInputWidget(
64 placeholder='Rhodecode'
63 placeholder='Rhodecode'
65 ),
64 ),
66 )
65 )
67 channel = colander.SchemaNode(
66 channel = colander.SchemaNode(
68 colander.String(),
67 colander.String(),
69 title=lazy_ugettext('Channel'),
68 title=_('Channel'),
70 description=lazy_ugettext('Channel to send notifications to.'),
69 description=_('Channel to send notifications to.'),
71 missing='',
70 missing='',
72 preparer=strip_whitespace,
71 preparer=strip_whitespace,
73 widget=deform.widget.TextInputWidget(
72 widget=deform.widget.TextInputWidget(
74 placeholder='#general'
73 placeholder='#general'
75 ),
74 ),
76 )
75 )
77 icon_emoji = colander.SchemaNode(
76 icon_emoji = colander.SchemaNode(
78 colander.String(),
77 colander.String(),
79 title=lazy_ugettext('Emoji'),
78 title=_('Emoji'),
80 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
79 description=_('Emoji to use eg. :studio_microphone:'),
81 missing='',
80 missing='',
82 preparer=strip_whitespace,
81 preparer=strip_whitespace,
83 widget=deform.widget.TextInputWidget(
82 widget=deform.widget.TextInputWidget(
84 placeholder=':studio_microphone:'
83 placeholder=':studio_microphone:'
85 ),
84 ),
86 )
85 )
87
86
88
87
89 repo_push_template = Template(r'''
88 repo_push_template = Template(r'''
90 *${data['actor']['username']}* pushed to \
89 *${data['actor']['username']}* pushed to \
91 %if data['push']['branches']:
90 %if data['push']['branches']:
92 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
91 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
93 ${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
92 ${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
94 %else:
93 %else:
95 unknown branch \
94 unknown branch \
96 %endif
95 %endif
97 in <${data['repo']['url']}|${data['repo']['repo_name']}>
96 in <${data['repo']['url']}|${data['repo']['repo_name']}>
98 >>>
97 >>>
99 %for commit in data['push']['commits']:
98 %for commit in data['push']['commits']:
100 <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
99 <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
101 %endfor
100 %endfor
102 ''')
101 ''')
103
102
104
103
104
105
105 class SlackIntegrationType(IntegrationTypeBase):
106 class SlackIntegrationType(IntegrationTypeBase):
106 key = 'slack'
107 key = 'slack'
107 display_name = lazy_ugettext('Slack')
108 display_name = _('Slack')
108 SettingsSchema = SlackSettingsSchema
109 description = _('Send events such as repo pushes and pull requests to '
110 'your slack channel.')
111 icon = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><g><path d="M165.963541,15.8384262 C162.07318,3.86308197 149.212328,-2.69009836 137.239082,1.20236066 C125.263738,5.09272131 118.710557,17.9535738 122.603016,29.9268197 L181.550164,211.292328 C185.597902,222.478689 197.682361,228.765377 209.282098,225.426885 C221.381246,221.943607 228.756984,209.093246 224.896,197.21023 C224.749115,196.756984 165.963541,15.8384262 165.963541,15.8384262" fill="#DFA22F"></path><path d="M74.6260984,45.515541 C70.7336393,33.5422951 57.8727869,26.9891148 45.899541,30.8794754 C33.9241967,34.7698361 27.3710164,47.6306885 31.2634754,59.6060328 L90.210623,240.971541 C94.2583607,252.157902 106.34282,258.44459 117.942557,255.104 C130.041705,251.62282 137.417443,238.772459 133.556459,226.887344 C133.409574,226.436197 74.6260984,45.515541 74.6260984,45.515541" fill="#3CB187"></path><path d="M240.161574,166.045377 C252.136918,162.155016 258.688,149.294164 254.797639,137.31882 C250.907279,125.345574 238.046426,118.792393 226.07318,122.682754 L44.7076721,181.632 C33.5213115,185.677639 27.234623,197.762098 30.5731148,209.361836 C34.0563934,221.460984 46.9067541,228.836721 58.7897705,224.975738 C59.2430164,224.828852 240.161574,166.045377 240.161574,166.045377" fill="#CE1E5B"></path><path d="M82.507541,217.270557 C94.312918,213.434754 109.528131,208.491016 125.855475,203.186361 C122.019672,191.380984 117.075934,176.163672 111.76918,159.83423 L68.4191475,173.924721 L82.507541,217.270557" fill="#392538"></path><path d="M173.847082,187.591344 C190.235279,182.267803 205.467279,177.31777 217.195016,173.507148 C213.359213,161.70177 208.413377,146.480262 203.106623,130.146623 L159.75659,144.237115 L173.847082,187.591344" fill="#BB242A"></path><path d="M210.484459,74.7058361 C222.457705,70.8154754 229.010885,57.954623 225.120525,45.9792787 C221.230164,34.0060328 208.369311,27.4528525 196.393967,31.3432131 L15.028459,90.292459 C3.84209836,94.3380984 -2.44459016,106.422557 0.896,118.022295 C4.37718033,130.121443 17.227541,137.49718 29.1126557,133.636197 C29.5638033,133.489311 210.484459,74.7058361 210.484459,74.7058361" fill="#72C5CD"></path><path d="M52.8220328,125.933115 C64.6274098,122.097311 79.8468197,117.151475 96.1762623,111.84682 C90.8527213,95.4565246 85.9026885,80.2245246 82.0920656,68.4946885 L38.731541,82.5872787 L52.8220328,125.933115" fill="#248C73"></path><path d="M144.159475,96.256 C160.551869,90.9303607 175.785967,85.9803279 187.515803,82.1676066 C182.190164,65.7752131 177.240131,50.5390164 173.42741,38.807082 L130.068984,52.8996721 L144.159475,96.256" fill="#62803A"></path></g></svg>'''
109 valid_events = [
112 valid_events = [
110 events.PullRequestCloseEvent,
113 events.PullRequestCloseEvent,
111 events.PullRequestMergeEvent,
114 events.PullRequestMergeEvent,
112 events.PullRequestUpdateEvent,
115 events.PullRequestUpdateEvent,
113 events.PullRequestCommentEvent,
116 events.PullRequestCommentEvent,
114 events.PullRequestReviewEvent,
117 events.PullRequestReviewEvent,
115 events.PullRequestCreateEvent,
118 events.PullRequestCreateEvent,
116 events.RepoPushEvent,
119 events.RepoPushEvent,
117 events.RepoCreateEvent,
120 events.RepoCreateEvent,
118 ]
121 ]
119
122
120 def send_event(self, event):
123 def send_event(self, event):
121 if event.__class__ not in self.valid_events:
124 if event.__class__ not in self.valid_events:
122 log.debug('event not valid: %r' % event)
125 log.debug('event not valid: %r' % event)
123 return
126 return
124
127
125 if event.name not in self.settings['events']:
128 if event.name not in self.settings['events']:
126 log.debug('event ignored: %r' % event)
129 log.debug('event ignored: %r' % event)
127 return
130 return
128
131
129 data = event.as_dict()
132 data = event.as_dict()
130
133
131 text = '*%s* caused a *%s* event' % (
134 text = '*%s* caused a *%s* event' % (
132 data['actor']['username'], event.name)
135 data['actor']['username'], event.name)
133
136
134 log.debug('handling slack event for %s' % event.name)
137 log.debug('handling slack event for %s' % event.name)
135
138
136 if isinstance(event, events.PullRequestCommentEvent):
139 if isinstance(event, events.PullRequestCommentEvent):
137 text = self.format_pull_request_comment_event(event, data)
140 text = self.format_pull_request_comment_event(event, data)
138 elif isinstance(event, events.PullRequestReviewEvent):
141 elif isinstance(event, events.PullRequestReviewEvent):
139 text = self.format_pull_request_review_event(event, data)
142 text = self.format_pull_request_review_event(event, data)
140 elif isinstance(event, events.PullRequestEvent):
143 elif isinstance(event, events.PullRequestEvent):
141 text = self.format_pull_request_event(event, data)
144 text = self.format_pull_request_event(event, data)
142 elif isinstance(event, events.RepoPushEvent):
145 elif isinstance(event, events.RepoPushEvent):
143 text = self.format_repo_push_event(data)
146 text = self.format_repo_push_event(data)
144 elif isinstance(event, events.RepoCreateEvent):
147 elif isinstance(event, events.RepoCreateEvent):
145 text = self.format_repo_create_event(data)
148 text = self.format_repo_create_event(data)
146 else:
149 else:
147 log.error('unhandled event type: %r' % event)
150 log.error('unhandled event type: %r' % event)
148
151
149 run_task(post_text_to_slack, self.settings, text)
152 run_task(post_text_to_slack, self.settings, text)
150
153
151 def settings_schema(self):
154 def settings_schema(self):
152 schema = SlackSettingsSchema()
155 schema = SlackSettingsSchema()
153 schema.add(colander.SchemaNode(
156 schema.add(colander.SchemaNode(
154 colander.Set(),
157 colander.Set(),
155 widget=deform.widget.CheckboxChoiceWidget(
158 widget=deform.widget.CheckboxChoiceWidget(
156 values=sorted(
159 values=sorted(
157 [(e.name, e.display_name) for e in self.valid_events]
160 [(e.name, e.display_name) for e in self.valid_events]
158 )
161 )
159 ),
162 ),
160 description="Events activated for this integration",
163 description="Events activated for this integration",
161 name='events'
164 name='events'
162 ))
165 ))
163
166
164 return schema
167 return schema
165
168
166 def format_pull_request_comment_event(self, event, data):
169 def format_pull_request_comment_event(self, event, data):
167 comment_text = data['comment']['text']
170 comment_text = data['comment']['text']
168 if len(comment_text) > 200:
171 if len(comment_text) > 200:
169 comment_text = '<{comment_url}|{comment_text}...>'.format(
172 comment_text = '<{comment_url}|{comment_text}...>'.format(
170 comment_text=comment_text[:200],
173 comment_text=comment_text[:200],
171 comment_url=data['comment']['url'],
174 comment_url=data['comment']['url'],
172 )
175 )
173
176
174 comment_status = ''
177 comment_status = ''
175 if data['comment']['status']:
178 if data['comment']['status']:
176 comment_status = '[{}]: '.format(data['comment']['status'])
179 comment_status = '[{}]: '.format(data['comment']['status'])
177
180
178 return (textwrap.dedent(
181 return (textwrap.dedent(
179 '''
182 '''
180 {user} commented on pull request <{pr_url}|#{number}> - {pr_title}:
183 {user} commented on pull request <{pr_url}|#{number}> - {pr_title}:
181 >>> {comment_status}{comment_text}
184 >>> {comment_status}{comment_text}
182 ''').format(
185 ''').format(
183 comment_status=comment_status,
186 comment_status=comment_status,
184 user=data['actor']['username'],
187 user=data['actor']['username'],
185 number=data['pullrequest']['pull_request_id'],
188 number=data['pullrequest']['pull_request_id'],
186 pr_url=data['pullrequest']['url'],
189 pr_url=data['pullrequest']['url'],
187 pr_status=data['pullrequest']['status'],
190 pr_status=data['pullrequest']['status'],
188 pr_title=data['pullrequest']['title'],
191 pr_title=data['pullrequest']['title'],
189 comment_text=comment_text
192 comment_text=comment_text
190 )
193 )
191 )
194 )
192
195
193 def format_pull_request_review_event(self, event, data):
196 def format_pull_request_review_event(self, event, data):
194 return (textwrap.dedent(
197 return (textwrap.dedent(
195 '''
198 '''
196 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
199 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
197 ''').format(
200 ''').format(
198 user=data['actor']['username'],
201 user=data['actor']['username'],
199 number=data['pullrequest']['pull_request_id'],
202 number=data['pullrequest']['pull_request_id'],
200 pr_url=data['pullrequest']['url'],
203 pr_url=data['pullrequest']['url'],
201 pr_status=data['pullrequest']['status'],
204 pr_status=data['pullrequest']['status'],
202 pr_title=data['pullrequest']['title'],
205 pr_title=data['pullrequest']['title'],
203 )
206 )
204 )
207 )
205
208
206 def format_pull_request_event(self, event, data):
209 def format_pull_request_event(self, event, data):
207 action = {
210 action = {
208 events.PullRequestCloseEvent: 'closed',
211 events.PullRequestCloseEvent: 'closed',
209 events.PullRequestMergeEvent: 'merged',
212 events.PullRequestMergeEvent: 'merged',
210 events.PullRequestUpdateEvent: 'updated',
213 events.PullRequestUpdateEvent: 'updated',
211 events.PullRequestCreateEvent: 'created',
214 events.PullRequestCreateEvent: 'created',
212 }.get(event.__class__, str(event.__class__))
215 }.get(event.__class__, str(event.__class__))
213
216
214 return ('Pull request <{url}|#{number}> - {title} '
217 return ('Pull request <{url}|#{number}> - {title} '
215 '{action} by {user}').format(
218 '{action} by {user}').format(
216 user=data['actor']['username'],
219 user=data['actor']['username'],
217 number=data['pullrequest']['pull_request_id'],
220 number=data['pullrequest']['pull_request_id'],
218 url=data['pullrequest']['url'],
221 url=data['pullrequest']['url'],
219 title=data['pullrequest']['title'],
222 title=data['pullrequest']['title'],
220 action=action
223 action=action
221 )
224 )
222
225
223 def format_repo_push_event(self, data):
226 def format_repo_push_event(self, data):
224 result = repo_push_template.render(
227 result = repo_push_template.render(
225 data=data,
228 data=data,
226 html_to_slack_links=html_to_slack_links,
229 html_to_slack_links=html_to_slack_links,
227 )
230 )
228 return result
231 return result
229
232
230 def format_repo_create_event(self, data):
233 def format_repo_create_event(self, data):
231 return '<{}|{}> ({}) repository created by *{}*'.format(
234 return '<{}|{}> ({}) repository created by *{}*'.format(
232 data['repo']['url'],
235 data['repo']['url'],
233 data['repo']['repo_name'],
236 data['repo']['repo_name'],
234 data['repo']['repo_type'],
237 data['repo']['repo_type'],
235 data['actor']['username'],
238 data['actor']['username'],
236 )
239 )
237
240
238
241
239 def html_to_slack_links(message):
242 def html_to_slack_links(message):
240 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
243 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
241 r'<\1|\2>', message)
244 r'<\1|\2>', message)
242
245
243
246
244 @task(ignore_result=True)
247 @task(ignore_result=True)
245 def post_text_to_slack(settings, text):
248 def post_text_to_slack(settings, text):
246 log.debug('sending %s to slack %s' % (text, settings['service']))
249 log.debug('sending %s to slack %s' % (text, settings['service']))
247 resp = requests.post(settings['service'], json={
250 resp = requests.post(settings['service'], json={
248 "channel": settings.get('channel', ''),
251 "channel": settings.get('channel', ''),
249 "username": settings.get('username', 'Rhodecode'),
252 "username": settings.get('username', 'Rhodecode'),
250 "text": text,
253 "text": text,
251 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
254 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
252 })
255 })
253 resp.raise_for_status() # raise exception on a failed request
256 resp.raise_for_status() # raise exception on a failed request
@@ -1,111 +1,117 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22
22
23 import deform
23 import deform
24 import logging
24 import logging
25 import requests
25 import requests
26 import colander
26 import colander
27 from celery.task import task
27 from celery.task import task
28 from mako.template import Template
28 from mako.template import Template
29
29
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.translation import lazy_ugettext
31 from rhodecode.translation import _
32 from rhodecode.integrations.types.base import IntegrationTypeBase
32 from rhodecode.integrations.types.base import IntegrationTypeBase
33 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
34
33
35 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
36
35
37
36
38 class WebhookSettingsSchema(IntegrationSettingsSchemaBase):
37 class WebhookSettingsSchema(colander.Schema):
39 url = colander.SchemaNode(
38 url = colander.SchemaNode(
40 colander.String(),
39 colander.String(),
41 title=lazy_ugettext('Webhook URL'),
40 title=_('Webhook URL'),
42 description=lazy_ugettext('URL of the webhook to receive POST event.'),
41 description=_('URL of the webhook to receive POST event.'),
43 default='',
42 missing=colander.required,
43 required=True,
44 validator=colander.url,
44 validator=colander.url,
45 widget=deform.widget.TextInputWidget(
45 widget=deform.widget.TextInputWidget(
46 placeholder='https://www.example.com/webhook'
46 placeholder='https://www.example.com/webhook'
47 ),
47 ),
48 )
48 )
49 secret_token = colander.SchemaNode(
49 secret_token = colander.SchemaNode(
50 colander.String(),
50 colander.String(),
51 title=lazy_ugettext('Secret Token'),
51 title=_('Secret Token'),
52 description=lazy_ugettext('String used to validate received payloads.'),
52 description=_('String used to validate received payloads.'),
53 default='',
53 default='',
54 missing='',
54 widget=deform.widget.TextInputWidget(
55 widget=deform.widget.TextInputWidget(
55 placeholder='secret_token'
56 placeholder='secret_token'
56 ),
57 ),
57 )
58 )
58
59
59
60
61
62
60 class WebhookIntegrationType(IntegrationTypeBase):
63 class WebhookIntegrationType(IntegrationTypeBase):
61 key = 'webhook'
64 key = 'webhook'
62 display_name = lazy_ugettext('Webhook')
65 display_name = _('Webhook')
66 description = _('Post json events to a webhook endpoint')
67 icon = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg viewBox="0 0 256 239" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid"><g><path d="M119.540432,100.502743 C108.930124,118.338815 98.7646301,135.611455 88.3876025,152.753617 C85.7226696,157.154315 84.4040417,160.738531 86.5332204,166.333309 C92.4107024,181.787152 84.1193605,196.825836 68.5350381,200.908244 C53.8383677,204.759349 39.5192953,195.099955 36.6032893,179.365384 C34.0194114,165.437749 44.8274148,151.78491 60.1824106,149.608284 C61.4694072,149.424428 62.7821041,149.402681 64.944891,149.240571 C72.469175,136.623655 80.1773157,123.700312 88.3025935,110.073173 C73.611854,95.4654658 64.8677898,78.3885437 66.803227,57.2292132 C68.1712787,42.2715849 74.0527146,29.3462646 84.8033863,18.7517722 C105.393354,-1.53572199 136.805164,-4.82141828 161.048542,10.7510424 C184.333097,25.7086706 194.996783,54.8450075 185.906752,79.7822957 C179.052655,77.9239597 172.151111,76.049808 164.563565,73.9917997 C167.418285,60.1274266 165.306899,47.6765751 155.95591,37.0109123 C149.777932,29.9690049 141.850349,26.2780332 132.835442,24.9178894 C114.764113,22.1877169 97.0209573,33.7983633 91.7563309,51.5355878 C85.7800012,71.6669027 94.8245623,88.1111998 119.540432,100.502743 L119.540432,100.502743 Z" fill="#C73A63"></path><path d="M149.841194,79.4106285 C157.316054,92.5969067 164.905578,105.982857 172.427885,119.246236 C210.44865,107.483365 239.114472,128.530009 249.398582,151.063322 C261.81978,178.282014 253.328765,210.520191 228.933162,227.312431 C203.893073,244.551464 172.226236,241.605803 150.040866,219.46195 C155.694953,214.729124 161.376716,209.974552 167.44794,204.895759 C189.360489,219.088306 208.525074,218.420096 222.753207,201.614016 C234.885769,187.277151 234.622834,165.900356 222.138374,151.863988 C207.730339,135.66681 188.431321,135.172572 165.103273,150.721309 C155.426087,133.553447 145.58086,116.521995 136.210101,99.2295848 C133.05093,93.4015266 129.561608,90.0209366 122.440622,88.7873178 C110.547271,86.7253555 102.868785,76.5124151 102.408155,65.0698097 C101.955433,53.7537294 108.621719,43.5249733 119.04224,39.5394355 C129.363912,35.5914599 141.476705,38.7783085 148.419765,47.554004 C154.093621,54.7244134 155.896602,62.7943365 152.911402,71.6372484 C152.081082,74.1025091 151.00562,76.4886916 149.841194,79.4106285 L149.841194,79.4106285 Z" fill="#4B4B4B"></path><path d="M167.706921,187.209935 L121.936499,187.209935 C117.54964,205.253587 108.074103,219.821756 91.7464461,229.085759 C79.0544063,236.285822 65.3738898,238.72736 50.8136292,236.376762 C24.0061432,232.053165 2.08568567,207.920497 0.156179306,180.745298 C-2.02835403,149.962159 19.1309765,122.599149 47.3341915,116.452801 C49.2814904,123.524363 51.2485589,130.663141 53.1958579,137.716911 C27.3195169,150.919004 18.3639187,167.553089 25.6054984,188.352614 C31.9811726,206.657224 50.0900643,216.690262 69.7528413,212.809503 C89.8327554,208.847688 99.9567329,192.160226 98.7211371,165.37844 C117.75722,165.37844 136.809118,165.180745 155.847178,165.475311 C163.280522,165.591951 169.019617,164.820939 174.620326,158.267339 C183.840836,147.48306 200.811003,148.455721 210.741239,158.640984 C220.88894,169.049642 220.402609,185.79839 209.663799,195.768166 C199.302587,205.38802 182.933414,204.874012 173.240413,194.508846 C171.247644,192.37176 169.677943,189.835329 167.706921,187.209935 L167.706921,187.209935 Z" fill="#4A4A4A"></path></g></svg>'''
68
63 valid_events = [
69 valid_events = [
64 events.PullRequestCloseEvent,
70 events.PullRequestCloseEvent,
65 events.PullRequestMergeEvent,
71 events.PullRequestMergeEvent,
66 events.PullRequestUpdateEvent,
72 events.PullRequestUpdateEvent,
67 events.PullRequestCommentEvent,
73 events.PullRequestCommentEvent,
68 events.PullRequestReviewEvent,
74 events.PullRequestReviewEvent,
69 events.PullRequestCreateEvent,
75 events.PullRequestCreateEvent,
70 events.RepoPushEvent,
76 events.RepoPushEvent,
71 events.RepoCreateEvent,
77 events.RepoCreateEvent,
72 ]
78 ]
73
79
74 def settings_schema(self):
80 def settings_schema(self):
75 schema = WebhookSettingsSchema()
81 schema = WebhookSettingsSchema()
76 schema.add(colander.SchemaNode(
82 schema.add(colander.SchemaNode(
77 colander.Set(),
83 colander.Set(),
78 widget=deform.widget.CheckboxChoiceWidget(
84 widget=deform.widget.CheckboxChoiceWidget(
79 values=sorted(
85 values=sorted(
80 [(e.name, e.display_name) for e in self.valid_events]
86 [(e.name, e.display_name) for e in self.valid_events]
81 )
87 )
82 ),
88 ),
83 description="Events activated for this integration",
89 description="Events activated for this integration",
84 name='events'
90 name='events'
85 ))
91 ))
86 return schema
92 return schema
87
93
88 def send_event(self, event):
94 def send_event(self, event):
89 log.debug('handling event %s with webhook integration %s',
95 log.debug('handling event %s with webhook integration %s',
90 event.name, self)
96 event.name, self)
91
97
92 if event.__class__ not in self.valid_events:
98 if event.__class__ not in self.valid_events:
93 log.debug('event not valid: %r' % event)
99 log.debug('event not valid: %r' % event)
94 return
100 return
95
101
96 if event.name not in self.settings['events']:
102 if event.name not in self.settings['events']:
97 log.debug('event ignored: %r' % event)
103 log.debug('event ignored: %r' % event)
98 return
104 return
99
105
100 data = event.as_dict()
106 data = event.as_dict()
101 post_to_webhook(data, self.settings)
107 post_to_webhook(data, self.settings)
102
108
103
109
104 @task(ignore_result=True)
110 @task(ignore_result=True)
105 def post_to_webhook(data, settings):
111 def post_to_webhook(data, settings):
106 log.debug('sending event:%s to webhook %s', data['name'], settings['url'])
112 log.debug('sending event:%s to webhook %s', data['name'], settings['url'])
107 resp = requests.post(settings['url'], json={
113 resp = requests.post(settings['url'], json={
108 'token': settings['secret_token'],
114 'token': settings['secret_token'],
109 'event': data
115 'event': data
110 })
116 })
111 resp.raise_for_status() # raise exception on a failed request
117 resp.raise_for_status() # raise exception on a failed request
@@ -1,299 +1,385 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import colander
22 import logging
23 import pylons
21 import pylons
24 import deform
22 import deform
23 import logging
24 import colander
25 import peppercorn
26 import webhelpers.paginate
25
27
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden
28 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27 from pyramid.renderers import render
29 from pyramid.renderers import render
28 from pyramid.response import Response
30 from pyramid.response import Response
29
31
30 from rhodecode.lib import auth
32 from rhodecode.lib import auth
31 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.helpers import Page
32 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
33 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
35 from rhodecode.admin.navigation import navigation_list
39 from rhodecode.admin.navigation import navigation_list
36 from rhodecode.translation import _
40 from rhodecode.translation import _
37 from rhodecode.integrations import integration_type_registry
41 from rhodecode.integrations import integration_type_registry
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
43 make_integration_schema)
38
44
39 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
40
46
41
47
42 class IntegrationSettingsViewBase(object):
48 class IntegrationSettingsViewBase(object):
43 """ Base Integration settings view used by both repo / global settings """
49 """ Base Integration settings view used by both repo / global settings """
44
50
45 def __init__(self, context, request):
51 def __init__(self, context, request):
46 self.context = context
52 self.context = context
47 self.request = request
53 self.request = request
48 self._load_general_context()
54 self._load_general_context()
49
55
50 if not self.perm_check(request.user):
56 if not self.perm_check(request.user):
51 raise HTTPForbidden()
57 raise HTTPForbidden()
52
58
53 def _load_general_context(self):
59 def _load_general_context(self):
54 """
60 """
55 This avoids boilerplate for repo/global+list/edit+views/templates
61 This avoids boilerplate for repo/global+list/edit+views/templates
56 by doing all possible contexts at the same time however it should
62 by doing all possible contexts at the same time however it should
57 be split up into separate functions once more "contexts" exist
63 be split up into separate functions once more "contexts" exist
58 """
64 """
59
65
60 self.IntegrationType = None
66 self.IntegrationType = None
61 self.repo = None
67 self.repo = None
62 self.repo_group = None
68 self.repo_group = None
63 self.integration = None
69 self.integration = None
64 self.integrations = {}
70 self.integrations = {}
65
71
66 request = self.request
72 request = self.request
67
73
68 if 'repo_name' in request.matchdict: # we're in a repo context
74 if 'repo_name' in request.matchdict: # in repo settings context
69 repo_name = request.matchdict['repo_name']
75 repo_name = request.matchdict['repo_name']
70 self.repo = Repository.get_by_repo_name(repo_name)
76 self.repo = Repository.get_by_repo_name(repo_name)
71
77
72 if 'repo_group_name' in request.matchdict: # we're in repo_group context
78 if 'repo_group_name' in request.matchdict: # in group settings context
73 repo_group_name = request.matchdict['repo_group_name']
79 repo_group_name = request.matchdict['repo_group_name']
74 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
75
81
76 if 'integration' in request.matchdict: # we're in integration context
82
83 if 'integration' in request.matchdict: # integration type context
77 integration_type = request.matchdict['integration']
84 integration_type = request.matchdict['integration']
78 self.IntegrationType = integration_type_registry[integration_type]
85 self.IntegrationType = integration_type_registry[integration_type]
79
86
80 if 'integration_id' in request.matchdict: # single integration context
87 if 'integration_id' in request.matchdict: # single integration context
81 integration_id = request.matchdict['integration_id']
88 integration_id = request.matchdict['integration_id']
82 self.integration = Integration.get(integration_id)
89 self.integration = Integration.get(integration_id)
83 else: # list integrations context
84 integrations = IntegrationModel().get_integrations(
85 repo=self.repo, repo_group=self.repo_group)
86
90
87 for integration in integrations:
91 # extra perms check just in case
88 self.integrations.setdefault(integration.integration_type, []
92 if not self._has_perms_for_integration(self.integration):
89 ).append(integration)
93 raise HTTPForbidden()
90
94
91 self.settings = self.integration and self.integration.settings or {}
95 self.settings = self.integration and self.integration.settings or {}
96 self.admin_view = not (self.repo or self.repo_group)
97
98 def _has_perms_for_integration(self, integration):
99 perms = self.request.user.permissions
100
101 if 'hg.admin' in perms['global']:
102 return True
103
104 if integration.repo:
105 return perms['repositories'].get(
106 integration.repo.repo_name) == 'repository.admin'
107
108 if integration.repo_group:
109 return perms['repositories_groups'].get(
110 integration.repo_group.group_name) == 'group.admin'
111
112 return False
92
113
93 def _template_c_context(self):
114 def _template_c_context(self):
94 # TODO: dan: this is a stopgap in order to inherit from current pylons
115 # TODO: dan: this is a stopgap in order to inherit from current pylons
95 # based admin/repo settings templates - this should be removed entirely
116 # based admin/repo settings templates - this should be removed entirely
96 # after port to pyramid
117 # after port to pyramid
97
118
98 c = pylons.tmpl_context
119 c = pylons.tmpl_context
99 c.active = 'integrations'
120 c.active = 'integrations'
100 c.rhodecode_user = self.request.user
121 c.rhodecode_user = self.request.user
101 c.repo = self.repo
122 c.repo = self.repo
102 c.repo_group = self.repo_group
123 c.repo_group = self.repo_group
103 c.repo_name = self.repo and self.repo.repo_name or None
124 c.repo_name = self.repo and self.repo.repo_name or None
104 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
125 c.repo_group_name = self.repo_group and self.repo_group.group_name or None
126
105 if self.repo:
127 if self.repo:
106 c.repo_info = self.repo
128 c.repo_info = self.repo
107 c.rhodecode_db_repo = self.repo
129 c.rhodecode_db_repo = self.repo
108 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
130 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
109 else:
131 else:
110 c.navlist = navigation_list(self.request)
132 c.navlist = navigation_list(self.request)
111
133
112 return c
134 return c
113
135
114 def _form_schema(self):
136 def _form_schema(self):
115 if self.integration:
137 schema = make_integration_schema(IntegrationType=self.IntegrationType,
116 settings = self.integration.settings
138 settings=self.settings)
117 else:
118 settings = {}
119 return self.IntegrationType(settings=settings).settings_schema()
120
139
121 def settings_get(self, defaults=None, errors=None, form=None):
140 # returns a clone, important if mutating the schema later
122 """
141 return schema.bind(
123 View that displays the plugin settings as a form.
142 permissions=self.request.user.permissions,
124 """
143 no_scope=not self.admin_view)
125 defaults = defaults or {}
144
126 errors = errors or {}
145
146 def _form_defaults(self):
147 defaults = {}
127
148
128 if self.integration:
149 if self.integration:
129 defaults = self.integration.settings or {}
150 defaults['settings'] = self.integration.settings or {}
130 defaults['name'] = self.integration.name
151 defaults['options'] = {
131 defaults['enabled'] = self.integration.enabled
152 'name': self.integration.name,
153 'enabled': self.integration.enabled,
154 'scope': self.integration.scope,
155 }
132 else:
156 else:
133 if self.repo:
157 if self.repo:
134 scope = _('{repo_name} repository').format(
158 scope = _('{repo_name} repository').format(
135 repo_name=self.repo.repo_name)
159 repo_name=self.repo.repo_name)
136 elif self.repo_group:
160 elif self.repo_group:
137 scope = _('{repo_group_name} repo group').format(
161 scope = _('{repo_group_name} repo group').format(
138 repo_group_name=self.repo_group.group_name)
162 repo_group_name=self.repo_group.group_name)
139 else:
163 else:
140 scope = _('Global')
164 scope = _('Global')
141
165
142 defaults['name'] = '{} {} integration'.format(scope,
166 defaults['options'] = {
143 self.IntegrationType.display_name)
167 'enabled': True,
144 defaults['enabled'] = True
168 'name': _('{name} integration').format(
169 name=self.IntegrationType.display_name),
170 }
171 if self.repo:
172 defaults['options']['scope'] = self.repo
173 elif self.repo_group:
174 defaults['options']['scope'] = self.repo_group
175
176 return defaults
145
177
146 schema = self._form_schema().bind(request=self.request)
178 def _delete_integration(self, integration):
179 Session().delete(self.integration)
180 Session().commit()
181 self.request.session.flash(
182 _('Integration {integration_name} deleted successfully.').format(
183 integration_name=self.integration.name),
184 queue='success')
185
186 if self.repo:
187 redirect_to = self.request.route_url(
188 'repo_integrations_home', repo_name=self.repo.repo_name)
189 elif self.repo_group:
190 redirect_to = self.request.route_url(
191 'repo_group_integrations_home',
192 repo_group_name=self.repo_group.group_name)
193 else:
194 redirect_to = self.request.route_url('global_integrations_home')
195 raise HTTPFound(redirect_to)
196
197 def settings_get(self, defaults=None, form=None):
198 """
199 View that displays the integration settings as a form.
200 """
201
202 defaults = defaults or self._form_defaults()
203 schema = self._form_schema()
147
204
148 if self.integration:
205 if self.integration:
149 buttons = ('submit', 'delete')
206 buttons = ('submit', 'delete')
150 else:
207 else:
151 buttons = ('submit',)
208 buttons = ('submit',)
152
209
153 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
210 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
154
211
155 for node in schema:
156 setting = self.settings.get(node.name)
157 if setting is not None:
158 defaults.setdefault(node.name, setting)
159 else:
160 if node.default:
161 defaults.setdefault(node.name, node.default)
162
163 template_context = {
212 template_context = {
164 'form': form,
213 'form': form,
165 'defaults': defaults,
166 'errors': errors,
167 'schema': schema,
168 'current_IntegrationType': self.IntegrationType,
214 'current_IntegrationType': self.IntegrationType,
169 'integration': self.integration,
215 'integration': self.integration,
170 'settings': self.settings,
171 'resource': self.context,
172 'c': self._template_c_context(),
216 'c': self._template_c_context(),
173 }
217 }
174
218
175 return template_context
219 return template_context
176
220
177 @auth.CSRFRequired()
221 @auth.CSRFRequired()
178 def settings_post(self):
222 def settings_post(self):
179 """
223 """
180 View that validates and stores the plugin settings.
224 View that validates and stores the integration settings.
181 """
225 """
182 if self.request.params.get('delete'):
226 controls = self.request.POST.items()
183 Session().delete(self.integration)
227 pstruct = peppercorn.parse(controls)
184 Session().commit()
228
185 self.request.session.flash(
229 if self.integration and pstruct.get('delete'):
186 _('Integration {integration_name} deleted successfully.').format(
230 return self._delete_integration(self.integration)
187 integration_name=self.integration.name),
231
188 queue='success')
232 schema = self._form_schema()
189 if self.repo:
233
190 redirect_to = self.request.route_url(
234 skip_settings_validation = False
191 'repo_integrations_home', repo_name=self.repo.repo_name)
235 if self.integration and 'enabled' not in pstruct.get('options', {}):
192 else:
236 skip_settings_validation = True
193 redirect_to = self.request.route_url('global_integrations_home')
237 schema['settings'].validator = None
194 raise HTTPFound(redirect_to)
238 for field in schema['settings'].children:
239 field.validator = None
240 field.missing = ''
195
241
196 schema = self._form_schema().bind(request=self.request)
242 if self.integration:
243 buttons = ('submit', 'delete')
244 else:
245 buttons = ('submit',)
197
246
198 form = deform.Form(schema, buttons=('submit', 'delete'))
247 form = deform.Form(schema, buttons=buttons)
199
248
200 params = {}
249 if not self.admin_view:
201 for node in schema.children:
250 # scope is read only field in these cases, and has to be added
202 if type(node.typ) in (colander.Set, colander.List):
251 options = pstruct.setdefault('options', {})
203 val = self.request.params.getall(node.name)
252 if 'scope' not in options:
204 else:
253 if self.repo:
205 val = self.request.params.get(node.name)
254 options['scope'] = 'repo:{}'.format(self.repo.repo_name)
206 if val:
255 elif self.repo_group:
207 params[node.name] = val
256 options['scope'] = 'repogroup:{}'.format(
257 self.repo_group.group_name)
208
258
209 controls = self.request.POST.items()
210 try:
259 try:
211 valid_data = form.validate(controls)
260 valid_data = form.validate_pstruct(pstruct)
212 except deform.ValidationFailure as e:
261 except deform.ValidationFailure as e:
213 self.request.session.flash(
262 self.request.session.flash(
214 _('Errors exist when saving integration settings. '
263 _('Errors exist when saving integration settings. '
215 'Please check the form inputs.'),
264 'Please check the form inputs.'),
216 queue='error')
265 queue='error')
217 return self.settings_get(errors={}, defaults=params, form=e)
266 return self.settings_get(form=e)
218
267
219 if not self.integration:
268 if not self.integration:
220 self.integration = Integration()
269 self.integration = Integration()
221 self.integration.integration_type = self.IntegrationType.key
270 self.integration.integration_type = self.IntegrationType.key
222 if self.repo:
223 self.integration.repo = self.repo
224 elif self.repo_group:
225 self.integration.repo_group = self.repo_group
226 Session().add(self.integration)
271 Session().add(self.integration)
227
272
228 self.integration.enabled = valid_data.pop('enabled', False)
273 scope = valid_data['options']['scope']
229 self.integration.name = valid_data.pop('name')
230 self.integration.settings = valid_data
231
274
275 IntegrationModel().update_integration(self.integration,
276 name=valid_data['options']['name'],
277 enabled=valid_data['options']['enabled'],
278 settings=valid_data['settings'],
279 scope=scope)
280
281 self.integration.settings = valid_data['settings']
232 Session().commit()
282 Session().commit()
233
234 # Display success message and redirect.
283 # Display success message and redirect.
235 self.request.session.flash(
284 self.request.session.flash(
236 _('Integration {integration_name} updated successfully.').format(
285 _('Integration {integration_name} updated successfully.').format(
237 integration_name=self.IntegrationType.display_name),
286 integration_name=self.IntegrationType.display_name),
238 queue='success')
287 queue='success')
239
288
240 if self.repo:
289
241 redirect_to = self.request.route_url(
290 # if integration scope changes, we must redirect to the right place
242 'repo_integrations_edit', repo_name=self.repo.repo_name,
291 # keeping in mind if the original view was for /repo/ or /_admin/
292 admin_view = not (self.repo or self.repo_group)
293
294 if isinstance(self.integration.scope, Repository) and not admin_view:
295 redirect_to = self.request.route_path(
296 'repo_integrations_edit',
297 repo_name=self.integration.scope.repo_name,
243 integration=self.integration.integration_type,
298 integration=self.integration.integration_type,
244 integration_id=self.integration.integration_id)
299 integration_id=self.integration.integration_id)
245 elif self.repo:
300 elif isinstance(self.integration.scope, RepoGroup) and not admin_view:
246 redirect_to = self.request.route_url(
301 redirect_to = self.request.route_path(
247 'repo_group_integrations_edit',
302 'repo_group_integrations_edit',
248 repo_group_name=self.repo_group.group_name,
303 repo_group_name=self.integration.scope.group_name,
249 integration=self.integration.integration_type,
304 integration=self.integration.integration_type,
250 integration_id=self.integration.integration_id)
305 integration_id=self.integration.integration_id)
251 else:
306 else:
252 redirect_to = self.request.route_url(
307 redirect_to = self.request.route_path(
253 'global_integrations_edit',
308 'global_integrations_edit',
254 integration=self.integration.integration_type,
309 integration=self.integration.integration_type,
255 integration_id=self.integration.integration_id)
310 integration_id=self.integration.integration_id)
256
311
257 return HTTPFound(redirect_to)
312 return HTTPFound(redirect_to)
258
313
259 def index(self):
314 def index(self):
260 current_integrations = self.integrations
315 """ List integrations """
261 if self.IntegrationType:
316 if self.repo:
262 current_integrations = {
317 scope = self.repo
263 self.IntegrationType.key: self.integrations.get(
318 elif self.repo_group:
264 self.IntegrationType.key, [])
319 scope = self.repo_group
265 }
320 else:
321 scope = 'all'
322
323 integrations = []
324
325 for integration in IntegrationModel().get_integrations(
326 scope=scope, IntegrationType=self.IntegrationType):
327
328 # extra permissions check *just in case*
329 if not self._has_perms_for_integration(integration):
330 continue
331 integrations.append(integration)
332
333 sort_arg = self.request.GET.get('sort', 'name:asc')
334 if ':' in sort_arg:
335 sort_field, sort_dir = sort_arg.split(':')
336 else:
337 sort_field = sort_arg, 'asc'
338
339 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
340
341 integrations.sort(
342 key=lambda x: getattr(x[1], sort_field), reverse=(sort_dir=='desc'))
343
344
345 page_url = webhelpers.paginate.PageURL(
346 self.request.path, self.request.GET)
347 page = safe_int(self.request.GET.get('page', 1), 1)
348
349 integrations = Page(integrations, page=page, items_per_page=10,
350 url=page_url)
266
351
267 template_context = {
352 template_context = {
353 'sort_field': sort_field,
354 'rev_sort_dir': sort_dir != 'desc' and 'desc' or 'asc',
268 'current_IntegrationType': self.IntegrationType,
355 'current_IntegrationType': self.IntegrationType,
269 'current_integrations': current_integrations,
356 'integrations_list': integrations,
270 'available_integrations': integration_type_registry,
357 'available_integrations': integration_type_registry,
271 'c': self._template_c_context()
358 'c': self._template_c_context(),
359 'request': self.request,
272 }
360 }
361 return template_context
273
362
274 if self.repo:
363 def new_integration(self):
275 html = render('rhodecode:templates/admin/integrations/list.html',
364 template_context = {
276 template_context,
365 'available_integrations': integration_type_registry,
277 request=self.request)
366 'c': self._template_c_context(),
278 else:
367 }
279 html = render('rhodecode:templates/admin/integrations/list.html',
368 return template_context
280 template_context,
281 request=self.request)
282
283 return Response(html)
284
285
369
286 class GlobalIntegrationsView(IntegrationSettingsViewBase):
370 class GlobalIntegrationsView(IntegrationSettingsViewBase):
287 def perm_check(self, user):
371 def perm_check(self, user):
288 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
372 return auth.HasPermissionAll('hg.admin').check_permissions(user=user)
289
373
290
374
291 class RepoIntegrationsView(IntegrationSettingsViewBase):
375 class RepoIntegrationsView(IntegrationSettingsViewBase):
292 def perm_check(self, user):
376 def perm_check(self, user):
293 return auth.HasRepoPermissionAll('repository.admin'
377 return auth.HasRepoPermissionAll('repository.admin'
294 )(repo_name=self.repo.repo_name, user=user)
378 )(repo_name=self.repo.repo_name, user=user)
295
379
380
296 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
381 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
297 def perm_check(self, user):
382 def perm_check(self, user):
298 return auth.HasRepoGroupPermissionAll('group.admin'
383 return auth.HasRepoGroupPermissionAll('group.admin'
299 )(group_name=self.repo_group.group_name, user=user)
384 )(group_name=self.repo_group.group_name, user=user)
385
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file renamed from rhodecode/templates/admin/integrations/edit.html to rhodecode/templates/admin/integrations/form.html
NO CONTENT: file renamed from rhodecode/templates/admin/integrations/edit.html to rhodecode/templates/admin/integrations/form.html
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now