##// END OF EJS Templates
Implementes #509 require SSL flag now works for both git and mercurial....
marcink -
r2668:f0851f37 beta
parent child Browse files
Show More
@@ -1,236 +1,251 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE
11 from paste.httpheaders import WWW_AUTHENTICATE
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 HasPermissionAnyMiddleware, CookieStoreWrapper
22 HasPermissionAnyMiddleware, CookieStoreWrapper
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.model import meta
24 from rhodecode.model import meta
25
25
26 from rhodecode.model.db import Repository
26 from rhodecode.model.db import Repository, RhodeCodeUi
27 from rhodecode.model.notification import NotificationModel
27 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 def _get_ip_addr(environ):
33 def _get_ip_addr(environ):
34 proxy_key = 'HTTP_X_REAL_IP'
34 proxy_key = 'HTTP_X_REAL_IP'
35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
36 def_key = 'REMOTE_ADDR'
36 def_key = 'REMOTE_ADDR'
37
37
38 ip = environ.get(proxy_key2)
38 ip = environ.get(proxy_key2)
39 if ip:
39 if ip:
40 return ip
40 return ip
41
41
42 ip = environ.get(proxy_key)
42 ip = environ.get(proxy_key)
43
43
44 if ip:
44 if ip:
45 return ip
45 return ip
46
46
47 ip = environ.get(def_key, '0.0.0.0')
47 ip = environ.get(def_key, '0.0.0.0')
48 return ip
48 return ip
49
49
50
50
51 def _get_access_path(environ):
51 def _get_access_path(environ):
52 path = environ.get('PATH_INFO')
52 path = environ.get('PATH_INFO')
53 org_req = environ.get('pylons.original_request')
53 org_req = environ.get('pylons.original_request')
54 if org_req:
54 if org_req:
55 path = org_req.environ.get('PATH_INFO')
55 path = org_req.environ.get('PATH_INFO')
56 return path
56 return path
57
57
58
58
59 class BasicAuth(AuthBasicAuthenticator):
59 class BasicAuth(AuthBasicAuthenticator):
60
60
61 def __init__(self, realm, authfunc, auth_http_code=None):
61 def __init__(self, realm, authfunc, auth_http_code=None):
62 self.realm = realm
62 self.realm = realm
63 self.authfunc = authfunc
63 self.authfunc = authfunc
64 self._rc_auth_http_code = auth_http_code
64 self._rc_auth_http_code = auth_http_code
65
65
66 def build_authentication(self):
66 def build_authentication(self):
67 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
67 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
68 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
68 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
69 # return 403 if alternative http return code is specified in
69 # return 403 if alternative http return code is specified in
70 # RhodeCode config
70 # RhodeCode config
71 return HTTPForbidden(headers=head)
71 return HTTPForbidden(headers=head)
72 return HTTPUnauthorized(headers=head)
72 return HTTPUnauthorized(headers=head)
73
73
74
74
75 class BaseVCSController(object):
75 class BaseVCSController(object):
76
76
77 def __init__(self, application, config):
77 def __init__(self, application, config):
78 self.application = application
78 self.application = application
79 self.config = config
79 self.config = config
80 # base path of repo locations
80 # base path of repo locations
81 self.basepath = self.config['base_path']
81 self.basepath = self.config['base_path']
82 #authenticate this mercurial request using authfunc
82 #authenticate this mercurial request using authfunc
83 self.authenticate = BasicAuth('', authfunc,
83 self.authenticate = BasicAuth('', authfunc,
84 config.get('auth_ret_code'))
84 config.get('auth_ret_code'))
85 self.ipaddr = '0.0.0.0'
85 self.ipaddr = '0.0.0.0'
86
86
87 def _handle_request(self, environ, start_response):
87 def _handle_request(self, environ, start_response):
88 raise NotImplementedError()
88 raise NotImplementedError()
89
89
90 def _get_by_id(self, repo_name):
90 def _get_by_id(self, repo_name):
91 """
91 """
92 Get's a special pattern _<ID> from clone url and tries to replace it
92 Get's a special pattern _<ID> from clone url and tries to replace it
93 with a repository_name for support of _<ID> non changable urls
93 with a repository_name for support of _<ID> non changable urls
94
94
95 :param repo_name:
95 :param repo_name:
96 """
96 """
97 try:
97 try:
98 data = repo_name.split('/')
98 data = repo_name.split('/')
99 if len(data) >= 2:
99 if len(data) >= 2:
100 by_id = data[1].split('_')
100 by_id = data[1].split('_')
101 if len(by_id) == 2 and by_id[1].isdigit():
101 if len(by_id) == 2 and by_id[1].isdigit():
102 _repo_name = Repository.get(by_id[1]).repo_name
102 _repo_name = Repository.get(by_id[1]).repo_name
103 data[1] = _repo_name
103 data[1] = _repo_name
104 except:
104 except:
105 log.debug('Failed to extract repo_name from id %s' % (
105 log.debug('Failed to extract repo_name from id %s' % (
106 traceback.format_exc()
106 traceback.format_exc()
107 )
107 )
108 )
108 )
109
109
110 return '/'.join(data)
110 return '/'.join(data)
111
111
112 def _invalidate_cache(self, repo_name):
112 def _invalidate_cache(self, repo_name):
113 """
113 """
114 Set's cache for this repository for invalidation on next access
114 Set's cache for this repository for invalidation on next access
115
115
116 :param repo_name: full repo name, also a cache key
116 :param repo_name: full repo name, also a cache key
117 """
117 """
118 invalidate_cache('get_repo_cached_%s' % repo_name)
118 invalidate_cache('get_repo_cached_%s' % repo_name)
119
119
120 def _check_permission(self, action, user, repo_name):
120 def _check_permission(self, action, user, repo_name):
121 """
121 """
122 Checks permissions using action (push/pull) user and repository
122 Checks permissions using action (push/pull) user and repository
123 name
123 name
124
124
125 :param action: push or pull action
125 :param action: push or pull action
126 :param user: user instance
126 :param user: user instance
127 :param repo_name: repository name
127 :param repo_name: repository name
128 """
128 """
129 if action == 'push':
129 if action == 'push':
130 if not HasPermissionAnyMiddleware('repository.write',
130 if not HasPermissionAnyMiddleware('repository.write',
131 'repository.admin')(user,
131 'repository.admin')(user,
132 repo_name):
132 repo_name):
133 return False
133 return False
134
134
135 else:
135 else:
136 #any other action need at least read permission
136 #any other action need at least read permission
137 if not HasPermissionAnyMiddleware('repository.read',
137 if not HasPermissionAnyMiddleware('repository.read',
138 'repository.write',
138 'repository.write',
139 'repository.admin')(user,
139 'repository.admin')(user,
140 repo_name):
140 repo_name):
141 return False
141 return False
142
142
143 return True
143 return True
144
144
145 def _get_ip_addr(self, environ):
145 def _get_ip_addr(self, environ):
146 return _get_ip_addr(environ)
146 return _get_ip_addr(environ)
147
147
148 def _check_ssl(self, environ, start_response):
149 """
150 Checks the SSL check flag and returns False if SSL is not present
151 and required True otherwise
152 """
153 org_proto = environ['wsgi._org_proto']
154 #check if we have SSL required ! if not it's a bad request !
155 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl')\
156 .scalar().ui_value)
157 if require_ssl and org_proto == 'http':
158 log.debug('proto is %s and SSL is required BAD REQUEST !'
159 % org_proto)
160 return False
161 return True
162
148 def __call__(self, environ, start_response):
163 def __call__(self, environ, start_response):
149 start = time.time()
164 start = time.time()
150 try:
165 try:
151 return self._handle_request(environ, start_response)
166 return self._handle_request(environ, start_response)
152 finally:
167 finally:
153 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
168 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
154 log.debug('Request time: %.3fs' % (time.time() - start))
169 log.debug('Request time: %.3fs' % (time.time() - start))
155 meta.Session.remove()
170 meta.Session.remove()
156
171
157
172
158 class BaseController(WSGIController):
173 class BaseController(WSGIController):
159
174
160 def __before__(self):
175 def __before__(self):
161 c.rhodecode_version = __version__
176 c.rhodecode_version = __version__
162 c.rhodecode_instanceid = config.get('instance_id')
177 c.rhodecode_instanceid = config.get('instance_id')
163 c.rhodecode_name = config.get('rhodecode_title')
178 c.rhodecode_name = config.get('rhodecode_title')
164 c.use_gravatar = str2bool(config.get('use_gravatar'))
179 c.use_gravatar = str2bool(config.get('use_gravatar'))
165 c.ga_code = config.get('rhodecode_ga_code')
180 c.ga_code = config.get('rhodecode_ga_code')
166 c.repo_name = get_repo_slug(request)
181 c.repo_name = get_repo_slug(request)
167 c.backends = BACKENDS.keys()
182 c.backends = BACKENDS.keys()
168 c.unread_notifications = NotificationModel()\
183 c.unread_notifications = NotificationModel()\
169 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
184 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
170 self.cut_off_limit = int(config.get('cut_off_limit'))
185 self.cut_off_limit = int(config.get('cut_off_limit'))
171
186
172 self.sa = meta.Session
187 self.sa = meta.Session
173 self.scm_model = ScmModel(self.sa)
188 self.scm_model = ScmModel(self.sa)
174 self.ip_addr = ''
189 self.ip_addr = ''
175
190
176 def __call__(self, environ, start_response):
191 def __call__(self, environ, start_response):
177 """Invoke the Controller"""
192 """Invoke the Controller"""
178 # WSGIController.__call__ dispatches to the Controller method
193 # WSGIController.__call__ dispatches to the Controller method
179 # the request is routed to. This routing information is
194 # the request is routed to. This routing information is
180 # available in environ['pylons.routes_dict']
195 # available in environ['pylons.routes_dict']
181 start = time.time()
196 start = time.time()
182 try:
197 try:
183 self.ip_addr = _get_ip_addr(environ)
198 self.ip_addr = _get_ip_addr(environ)
184 # make sure that we update permissions each time we call controller
199 # make sure that we update permissions each time we call controller
185 api_key = request.GET.get('api_key')
200 api_key = request.GET.get('api_key')
186 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
201 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
187 user_id = cookie_store.get('user_id', None)
202 user_id = cookie_store.get('user_id', None)
188 username = get_container_username(environ, config)
203 username = get_container_username(environ, config)
189 auth_user = AuthUser(user_id, api_key, username)
204 auth_user = AuthUser(user_id, api_key, username)
190 request.user = auth_user
205 request.user = auth_user
191 self.rhodecode_user = c.rhodecode_user = auth_user
206 self.rhodecode_user = c.rhodecode_user = auth_user
192 if not self.rhodecode_user.is_authenticated and \
207 if not self.rhodecode_user.is_authenticated and \
193 self.rhodecode_user.user_id is not None:
208 self.rhodecode_user.user_id is not None:
194 self.rhodecode_user.set_authenticated(
209 self.rhodecode_user.set_authenticated(
195 cookie_store.get('is_authenticated')
210 cookie_store.get('is_authenticated')
196 )
211 )
197 log.info('IP: %s User: %s accessed %s' % (
212 log.info('IP: %s User: %s accessed %s' % (
198 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
213 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
199 )
214 )
200 return WSGIController.__call__(self, environ, start_response)
215 return WSGIController.__call__(self, environ, start_response)
201 finally:
216 finally:
202 log.info('IP: %s Request to %s time: %.3fs' % (
217 log.info('IP: %s Request to %s time: %.3fs' % (
203 _get_ip_addr(environ),
218 _get_ip_addr(environ),
204 safe_unicode(_get_access_path(environ)), time.time() - start)
219 safe_unicode(_get_access_path(environ)), time.time() - start)
205 )
220 )
206 meta.Session.remove()
221 meta.Session.remove()
207
222
208
223
209 class BaseRepoController(BaseController):
224 class BaseRepoController(BaseController):
210 """
225 """
211 Base class for controllers responsible for loading all needed data for
226 Base class for controllers responsible for loading all needed data for
212 repository loaded items are
227 repository loaded items are
213
228
214 c.rhodecode_repo: instance of scm repository
229 c.rhodecode_repo: instance of scm repository
215 c.rhodecode_db_repo: instance of db
230 c.rhodecode_db_repo: instance of db
216 c.repository_followers: number of followers
231 c.repository_followers: number of followers
217 c.repository_forks: number of forks
232 c.repository_forks: number of forks
218 """
233 """
219
234
220 def __before__(self):
235 def __before__(self):
221 super(BaseRepoController, self).__before__()
236 super(BaseRepoController, self).__before__()
222 if c.repo_name:
237 if c.repo_name:
223
238
224 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
239 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
225 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
240 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
226
241
227 if c.rhodecode_repo is None:
242 if c.rhodecode_repo is None:
228 log.error('%s this repository is present in database but it '
243 log.error('%s this repository is present in database but it '
229 'cannot be created as an scm instance', c.repo_name)
244 'cannot be created as an scm instance', c.repo_name)
230
245
231 redirect(url('home'))
246 redirect(url('home'))
232
247
233 # some globals counter for menu
248 # some globals counter for menu
234 c.repository_followers = self.scm_model.get_followers(dbr)
249 c.repository_followers = self.scm_model.get_followers(dbr)
235 c.repository_forks = self.scm_model.get_forks(dbr)
250 c.repository_forks = self.scm_model.get_forks(dbr)
236 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
251 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,62 +1,61 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.https_fixup
3 rhodecode.lib.middleware.https_fixup
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 middleware to handle https correctly
6 middleware to handle https correctly
7
7
8 :created_on: May 23, 2010
8 :created_on: May 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 from rhodecode.lib.utils2 import str2bool
26 from rhodecode.lib.utils2 import str2bool
27
27
28
28
29 class HttpsFixup(object):
29 class HttpsFixup(object):
30
30
31 def __init__(self, app, config):
31 def __init__(self, app, config):
32 self.application = app
32 self.application = app
33 self.config = config
33 self.config = config
34
34
35 def __call__(self, environ, start_response):
35 def __call__(self, environ, start_response):
36 self.__fixup(environ)
36 self.__fixup(environ)
37 return self.application(environ, start_response)
37 return self.application(environ, start_response)
38
38
39 def __fixup(self, environ):
39 def __fixup(self, environ):
40 """
40 """
41 Function to fixup the environ as needed. In order to use this
41 Function to fixup the environ as needed. In order to use this
42 middleware you should set this header inside your
42 middleware you should set this header inside your
43 proxy ie. nginx, apache etc.
43 proxy ie. nginx, apache etc.
44 """
44 """
45
45 # DETECT PROTOCOL !
46 if str2bool(self.config.get('force_https')):
47 proto = 'https'
48 else:
49 if 'HTTP_X_URL_SCHEME' in environ:
46 if 'HTTP_X_URL_SCHEME' in environ:
50 proto = environ.get('HTTP_X_URL_SCHEME')
47 proto = environ.get('HTTP_X_URL_SCHEME')
51 elif 'HTTP_X_FORWARDED_SCHEME' in environ:
48 elif 'HTTP_X_FORWARDED_SCHEME' in environ:
52 proto = environ.get('HTTP_X_FORWARDED_SCHEME')
49 proto = environ.get('HTTP_X_FORWARDED_SCHEME')
53 elif 'HTTP_X_FORWARDED_PROTO' in environ:
50 elif 'HTTP_X_FORWARDED_PROTO' in environ:
54 proto = environ.get('HTTP_X_FORWARDED_PROTO')
51 proto = environ.get('HTTP_X_FORWARDED_PROTO')
55 else:
52 else:
56 proto = 'http'
53 proto = 'http'
57 if proto == 'https':
54 org_proto = proto
55
56 # if we have force, just override
57 if str2bool(self.config.get('force_https')):
58 proto = 'https'
59
58 environ['wsgi.url_scheme'] = proto
60 environ['wsgi.url_scheme'] = proto
59 else:
61 environ['wsgi._org_proto'] = org_proto
60 environ['wsgi.url_scheme'] = 'http'
61
62 return None
@@ -1,305 +1,306 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33 from dulwich.web import LimitedInputFilter, GunzipFilter
33 from dulwich.web import LimitedInputFilter, GunzipFilter
34
34
35
35
36 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
37
37
38 def handle(self):
38 def handle(self):
39 write = lambda x: self.proto.write_sideband(1, x)
39 write = lambda x: self.proto.write_sideband(1, x)
40
40
41 graph_walker = dulserver.ProtocolGraphWalker(self,
41 graph_walker = dulserver.ProtocolGraphWalker(self,
42 self.repo.object_store,
42 self.repo.object_store,
43 self.repo.get_peeled)
43 self.repo.get_peeled)
44 objects_iter = self.repo.fetch_objects(
44 objects_iter = self.repo.fetch_objects(
45 graph_walker.determine_wants, graph_walker, self.progress,
45 graph_walker.determine_wants, graph_walker, self.progress,
46 get_tagged=self.get_tagged)
46 get_tagged=self.get_tagged)
47
47
48 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
48 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
49 # that the client still expects a 0-object pack in most cases.
49 # that the client still expects a 0-object pack in most cases.
50 if objects_iter is None:
50 if objects_iter is None:
51 return
51 return
52
52
53 self.progress("counting objects: %d, done.\n" % len(objects_iter))
53 self.progress("counting objects: %d, done.\n" % len(objects_iter))
54 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
54 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
55 objects_iter)
55 objects_iter)
56 messages = []
56 messages = []
57 messages.append('thank you for using rhodecode')
57 messages.append('thank you for using rhodecode')
58
58
59 for msg in messages:
59 for msg in messages:
60 self.progress(msg + "\n")
60 self.progress(msg + "\n")
61 # we are done
61 # we are done
62 self.proto.write("0000")
62 self.proto.write("0000")
63
63
64
64
65 dulserver.DEFAULT_HANDLERS = {
65 dulserver.DEFAULT_HANDLERS = {
66 #git-ls-remote, git-clone, git-fetch and git-pull
66 #git-ls-remote, git-clone, git-fetch and git-pull
67 'git-upload-pack': SimpleGitUploadPackHandler,
67 'git-upload-pack': SimpleGitUploadPackHandler,
68 #git-push
68 #git-push
69 'git-receive-pack': dulserver.ReceivePackHandler,
69 'git-receive-pack': dulserver.ReceivePackHandler,
70 }
70 }
71
71
72 # not used for now until dulwich get's fixed
72 # not used for now until dulwich get's fixed
73 #from dulwich.repo import Repo
73 #from dulwich.repo import Repo
74 #from dulwich.web import make_wsgi_chain
74 #from dulwich.web import make_wsgi_chain
75
75
76 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
76 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
78 HTTPBadRequest, HTTPNotAcceptable
77
79
78 from rhodecode.lib.utils2 import safe_str
80 from rhodecode.lib.utils2 import safe_str
79 from rhodecode.lib.base import BaseVCSController
81 from rhodecode.lib.base import BaseVCSController
80 from rhodecode.lib.auth import get_container_username
82 from rhodecode.lib.auth import get_container_username
81 from rhodecode.lib.utils import is_valid_repo, make_ui
83 from rhodecode.lib.utils import is_valid_repo, make_ui
82 from rhodecode.model.db import User, RhodeCodeUi
84 from rhodecode.model.db import User, RhodeCodeUi
83
85
84 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
85
86 log = logging.getLogger(__name__)
86 log = logging.getLogger(__name__)
87
87
88
88
89 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
89 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
90
90
91
91
92 def is_git(environ):
92 def is_git(environ):
93 path_info = environ['PATH_INFO']
93 path_info = environ['PATH_INFO']
94 isgit_path = GIT_PROTO_PAT.match(path_info)
94 isgit_path = GIT_PROTO_PAT.match(path_info)
95 log.debug('pathinfo: %s detected as GIT %s' % (
95 log.debug('pathinfo: %s detected as GIT %s' % (
96 path_info, isgit_path != None)
96 path_info, isgit_path != None)
97 )
97 )
98 return isgit_path
98 return isgit_path
99
99
100
100
101 class SimpleGit(BaseVCSController):
101 class SimpleGit(BaseVCSController):
102
102
103 def _handle_request(self, environ, start_response):
103 def _handle_request(self, environ, start_response):
104
104
105 if not is_git(environ):
105 if not is_git(environ):
106 return self.application(environ, start_response)
106 return self.application(environ, start_response)
107
107 if not self._check_ssl(environ, start_response):
108 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
108 ipaddr = self._get_ip_addr(environ)
109 ipaddr = self._get_ip_addr(environ)
109 username = None
110 username = None
110 self._git_first_op = False
111 self._git_first_op = False
111 # skip passing error to error controller
112 # skip passing error to error controller
112 environ['pylons.status_code_redirect'] = True
113 environ['pylons.status_code_redirect'] = True
113
114
114 #======================================================================
115 #======================================================================
115 # EXTRACT REPOSITORY NAME FROM ENV
116 # EXTRACT REPOSITORY NAME FROM ENV
116 #======================================================================
117 #======================================================================
117 try:
118 try:
118 repo_name = self.__get_repository(environ)
119 repo_name = self.__get_repository(environ)
119 log.debug('Extracted repo name is %s' % repo_name)
120 log.debug('Extracted repo name is %s' % repo_name)
120 except:
121 except:
121 return HTTPInternalServerError()(environ, start_response)
122 return HTTPInternalServerError()(environ, start_response)
122
123
123 # quick check if that dir exists...
124 # quick check if that dir exists...
124 if is_valid_repo(repo_name, self.basepath) is False:
125 if is_valid_repo(repo_name, self.basepath) is False:
125 return HTTPNotFound()(environ, start_response)
126 return HTTPNotFound()(environ, start_response)
126
127
127 #======================================================================
128 #======================================================================
128 # GET ACTION PULL or PUSH
129 # GET ACTION PULL or PUSH
129 #======================================================================
130 #======================================================================
130 action = self.__get_action(environ)
131 action = self.__get_action(environ)
131
132
132 #======================================================================
133 #======================================================================
133 # CHECK ANONYMOUS PERMISSION
134 # CHECK ANONYMOUS PERMISSION
134 #======================================================================
135 #======================================================================
135 if action in ['pull', 'push']:
136 if action in ['pull', 'push']:
136 anonymous_user = self.__get_user('default')
137 anonymous_user = self.__get_user('default')
137 username = anonymous_user.username
138 username = anonymous_user.username
138 anonymous_perm = self._check_permission(action, anonymous_user,
139 anonymous_perm = self._check_permission(action, anonymous_user,
139 repo_name)
140 repo_name)
140
141
141 if anonymous_perm is not True or anonymous_user.active is False:
142 if anonymous_perm is not True or anonymous_user.active is False:
142 if anonymous_perm is not True:
143 if anonymous_perm is not True:
143 log.debug('Not enough credentials to access this '
144 log.debug('Not enough credentials to access this '
144 'repository as anonymous user')
145 'repository as anonymous user')
145 if anonymous_user.active is False:
146 if anonymous_user.active is False:
146 log.debug('Anonymous access is disabled, running '
147 log.debug('Anonymous access is disabled, running '
147 'authentication')
148 'authentication')
148 #==============================================================
149 #==============================================================
149 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
150 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
150 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
151 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
151 #==============================================================
152 #==============================================================
152
153
153 # Attempting to retrieve username from the container
154 # Attempting to retrieve username from the container
154 username = get_container_username(environ, self.config)
155 username = get_container_username(environ, self.config)
155
156
156 # If not authenticated by the container, running basic auth
157 # If not authenticated by the container, running basic auth
157 if not username:
158 if not username:
158 self.authenticate.realm = \
159 self.authenticate.realm = \
159 safe_str(self.config['rhodecode_realm'])
160 safe_str(self.config['rhodecode_realm'])
160 result = self.authenticate(environ)
161 result = self.authenticate(environ)
161 if isinstance(result, str):
162 if isinstance(result, str):
162 AUTH_TYPE.update(environ, 'basic')
163 AUTH_TYPE.update(environ, 'basic')
163 REMOTE_USER.update(environ, result)
164 REMOTE_USER.update(environ, result)
164 username = result
165 username = result
165 else:
166 else:
166 return result.wsgi_application(environ, start_response)
167 return result.wsgi_application(environ, start_response)
167
168
168 #==============================================================
169 #==============================================================
169 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
170 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
170 #==============================================================
171 #==============================================================
171 try:
172 try:
172 user = self.__get_user(username)
173 user = self.__get_user(username)
173 if user is None or not user.active:
174 if user is None or not user.active:
174 return HTTPForbidden()(environ, start_response)
175 return HTTPForbidden()(environ, start_response)
175 username = user.username
176 username = user.username
176 except:
177 except:
177 log.error(traceback.format_exc())
178 log.error(traceback.format_exc())
178 return HTTPInternalServerError()(environ, start_response)
179 return HTTPInternalServerError()(environ, start_response)
179
180
180 #check permissions for this repository
181 #check permissions for this repository
181 perm = self._check_permission(action, user, repo_name)
182 perm = self._check_permission(action, user, repo_name)
182 if perm is not True:
183 if perm is not True:
183 return HTTPForbidden()(environ, start_response)
184 return HTTPForbidden()(environ, start_response)
184
185
185 extras = {
186 extras = {
186 'ip': ipaddr,
187 'ip': ipaddr,
187 'username': username,
188 'username': username,
188 'action': action,
189 'action': action,
189 'repository': repo_name,
190 'repository': repo_name,
190 'scm': 'git',
191 'scm': 'git',
191 }
192 }
192
193
193 #===================================================================
194 #===================================================================
194 # GIT REQUEST HANDLING
195 # GIT REQUEST HANDLING
195 #===================================================================
196 #===================================================================
196 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
197 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
197 log.debug('Repository path is %s' % repo_path)
198 log.debug('Repository path is %s' % repo_path)
198
199
199 baseui = make_ui('db')
200 baseui = make_ui('db')
200 self.__inject_extras(repo_path, baseui, extras)
201 self.__inject_extras(repo_path, baseui, extras)
201
202
202 try:
203 try:
203 # invalidate cache on push
204 # invalidate cache on push
204 if action == 'push':
205 if action == 'push':
205 self._invalidate_cache(repo_name)
206 self._invalidate_cache(repo_name)
206 self._handle_githooks(repo_name, action, baseui, environ)
207 self._handle_githooks(repo_name, action, baseui, environ)
207
208
208 log.info('%s action on GIT repo "%s"' % (action, repo_name))
209 log.info('%s action on GIT repo "%s"' % (action, repo_name))
209 app = self.__make_app(repo_name, repo_path, username)
210 app = self.__make_app(repo_name, repo_path, username)
210 return app(environ, start_response)
211 return app(environ, start_response)
211 except Exception:
212 except Exception:
212 log.error(traceback.format_exc())
213 log.error(traceback.format_exc())
213 return HTTPInternalServerError()(environ, start_response)
214 return HTTPInternalServerError()(environ, start_response)
214
215
215 def __make_app(self, repo_name, repo_path, username):
216 def __make_app(self, repo_name, repo_path, username):
216 """
217 """
217 Make an wsgi application using dulserver
218 Make an wsgi application using dulserver
218
219
219 :param repo_name: name of the repository
220 :param repo_name: name of the repository
220 :param repo_path: full path to the repository
221 :param repo_path: full path to the repository
221 """
222 """
222
223
223 from rhodecode.lib.middleware.pygrack import make_wsgi_app
224 from rhodecode.lib.middleware.pygrack import make_wsgi_app
224 app = make_wsgi_app(
225 app = make_wsgi_app(
225 repo_root=safe_str(self.basepath),
226 repo_root=safe_str(self.basepath),
226 repo_name=repo_name,
227 repo_name=repo_name,
227 username=username,
228 username=username,
228 )
229 )
229 app = GunzipFilter(LimitedInputFilter(app))
230 app = GunzipFilter(LimitedInputFilter(app))
230 return app
231 return app
231
232
232 def __get_repository(self, environ):
233 def __get_repository(self, environ):
233 """
234 """
234 Get's repository name out of PATH_INFO header
235 Get's repository name out of PATH_INFO header
235
236
236 :param environ: environ where PATH_INFO is stored
237 :param environ: environ where PATH_INFO is stored
237 """
238 """
238 try:
239 try:
239 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
240 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
240 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
241 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
241 except:
242 except:
242 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
243 raise
244 raise
244
245
245 return repo_name
246 return repo_name
246
247
247 def __get_user(self, username):
248 def __get_user(self, username):
248 return User.get_by_username(username)
249 return User.get_by_username(username)
249
250
250 def __get_action(self, environ):
251 def __get_action(self, environ):
251 """
252 """
252 Maps git request commands into a pull or push command.
253 Maps git request commands into a pull or push command.
253
254
254 :param environ:
255 :param environ:
255 """
256 """
256 service = environ['QUERY_STRING'].split('=')
257 service = environ['QUERY_STRING'].split('=')
257
258
258 if len(service) > 1:
259 if len(service) > 1:
259 service_cmd = service[1]
260 service_cmd = service[1]
260 mapping = {
261 mapping = {
261 'git-receive-pack': 'push',
262 'git-receive-pack': 'push',
262 'git-upload-pack': 'pull',
263 'git-upload-pack': 'pull',
263 }
264 }
264 op = mapping[service_cmd]
265 op = mapping[service_cmd]
265 self._git_stored_op = op
266 self._git_stored_op = op
266 return op
267 return op
267 else:
268 else:
268 # try to fallback to stored variable as we don't know if the last
269 # try to fallback to stored variable as we don't know if the last
269 # operation is pull/push
270 # operation is pull/push
270 op = getattr(self, '_git_stored_op', 'pull')
271 op = getattr(self, '_git_stored_op', 'pull')
271 return op
272 return op
272
273
273 def _handle_githooks(self, repo_name, action, baseui, environ):
274 def _handle_githooks(self, repo_name, action, baseui, environ):
274 """
275 """
275 Handles pull action, push is handled by post-receive hook
276 Handles pull action, push is handled by post-receive hook
276 """
277 """
277 from rhodecode.lib.hooks import log_pull_action
278 from rhodecode.lib.hooks import log_pull_action
278 service = environ['QUERY_STRING'].split('=')
279 service = environ['QUERY_STRING'].split('=')
279 if len(service) < 2:
280 if len(service) < 2:
280 return
281 return
281
282
282 from rhodecode.model.db import Repository
283 from rhodecode.model.db import Repository
283 _repo = Repository.get_by_repo_name(repo_name)
284 _repo = Repository.get_by_repo_name(repo_name)
284 _repo = _repo.scm_instance
285 _repo = _repo.scm_instance
285 _repo._repo.ui = baseui
286 _repo._repo.ui = baseui
286
287
287 _hooks = dict(baseui.configitems('hooks')) or {}
288 _hooks = dict(baseui.configitems('hooks')) or {}
288 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
289 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
289 log_pull_action(ui=baseui, repo=_repo._repo)
290 log_pull_action(ui=baseui, repo=_repo._repo)
290
291
291 def __inject_extras(self, repo_path, baseui, extras={}):
292 def __inject_extras(self, repo_path, baseui, extras={}):
292 """
293 """
293 Injects some extra params into baseui instance
294 Injects some extra params into baseui instance
294
295
295 :param baseui: baseui instance
296 :param baseui: baseui instance
296 :param extras: dict with extra params to put into baseui
297 :param extras: dict with extra params to put into baseui
297 """
298 """
298
299
299 # make our hgweb quiet so it doesn't print output
300 # make our hgweb quiet so it doesn't print output
300 baseui.setconfig('ui', 'quiet', 'true')
301 baseui.setconfig('ui', 'quiet', 'true')
301
302
302 #inject some additional parameters that will be available in ui
303 #inject some additional parameters that will be available in ui
303 #for hooks
304 #for hooks
304 for k, v in extras.items():
305 for k, v in extras.items():
305 baseui.setconfig('rhodecode_extras', k, v)
306 baseui.setconfig('rhodecode_extras', k, v)
@@ -1,258 +1,261 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30 import urllib
31
31
32 from mercurial.error import RepoError
32 from mercurial.error import RepoError
33 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
34
34
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
37 HTTPBadRequest, HTTPNotAcceptable
36
38
37 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.base import BaseVCSController
40 from rhodecode.lib.base import BaseVCSController
39 from rhodecode.lib.auth import get_container_username
41 from rhodecode.lib.auth import get_container_username
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
42 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
41 from rhodecode.model.db import User
43 from rhodecode.model.db import User
42
44
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47
48
48 def is_mercurial(environ):
49 def is_mercurial(environ):
49 """
50 """
50 Returns True if request's target is mercurial server - header
51 Returns True if request's target is mercurial server - header
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 """
53 """
53 http_accept = environ.get('HTTP_ACCEPT')
54 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
55 path_info = environ['PATH_INFO']
55 if http_accept and http_accept.startswith('application/mercurial'):
56 if http_accept and http_accept.startswith('application/mercurial'):
56 ishg_path = True
57 ishg_path = True
57 else:
58 else:
58 ishg_path = False
59 ishg_path = False
59
60
60 log.debug('pathinfo: %s detected as HG %s' % (
61 log.debug('pathinfo: %s detected as HG %s' % (
61 path_info, ishg_path)
62 path_info, ishg_path)
62 )
63 )
63 return ishg_path
64 return ishg_path
64
65
65
66
66 class SimpleHg(BaseVCSController):
67 class SimpleHg(BaseVCSController):
67
68
68 def _handle_request(self, environ, start_response):
69 def _handle_request(self, environ, start_response):
69 if not is_mercurial(environ):
70 if not is_mercurial(environ):
70 return self.application(environ, start_response)
71 return self.application(environ, start_response)
72 if not self._check_ssl(environ, start_response):
73 return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response)
71
74
72 ipaddr = self._get_ip_addr(environ)
75 ipaddr = self._get_ip_addr(environ)
73 username = None
76 username = None
74 # skip passing error to error controller
77 # skip passing error to error controller
75 environ['pylons.status_code_redirect'] = True
78 environ['pylons.status_code_redirect'] = True
76
79
77 #======================================================================
80 #======================================================================
78 # EXTRACT REPOSITORY NAME FROM ENV
81 # EXTRACT REPOSITORY NAME FROM ENV
79 #======================================================================
82 #======================================================================
80 try:
83 try:
81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
82 log.debug('Extracted repo name is %s' % repo_name)
85 log.debug('Extracted repo name is %s' % repo_name)
83 except:
86 except:
84 return HTTPInternalServerError()(environ, start_response)
87 return HTTPInternalServerError()(environ, start_response)
85
88
86 # quick check if that dir exists...
89 # quick check if that dir exists...
87 if is_valid_repo(repo_name, self.basepath) is False:
90 if is_valid_repo(repo_name, self.basepath) is False:
88 return HTTPNotFound()(environ, start_response)
91 return HTTPNotFound()(environ, start_response)
89
92
90 #======================================================================
93 #======================================================================
91 # GET ACTION PULL or PUSH
94 # GET ACTION PULL or PUSH
92 #======================================================================
95 #======================================================================
93 action = self.__get_action(environ)
96 action = self.__get_action(environ)
94
97
95 #======================================================================
98 #======================================================================
96 # CHECK ANONYMOUS PERMISSION
99 # CHECK ANONYMOUS PERMISSION
97 #======================================================================
100 #======================================================================
98 if action in ['pull', 'push']:
101 if action in ['pull', 'push']:
99 anonymous_user = self.__get_user('default')
102 anonymous_user = self.__get_user('default')
100 username = anonymous_user.username
103 username = anonymous_user.username
101 anonymous_perm = self._check_permission(action, anonymous_user,
104 anonymous_perm = self._check_permission(action, anonymous_user,
102 repo_name)
105 repo_name)
103
106
104 if anonymous_perm is not True or anonymous_user.active is False:
107 if anonymous_perm is not True or anonymous_user.active is False:
105 if anonymous_perm is not True:
108 if anonymous_perm is not True:
106 log.debug('Not enough credentials to access this '
109 log.debug('Not enough credentials to access this '
107 'repository as anonymous user')
110 'repository as anonymous user')
108 if anonymous_user.active is False:
111 if anonymous_user.active is False:
109 log.debug('Anonymous access is disabled, running '
112 log.debug('Anonymous access is disabled, running '
110 'authentication')
113 'authentication')
111 #==============================================================
114 #==============================================================
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
115 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
116 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 #==============================================================
117 #==============================================================
115
118
116 # Attempting to retrieve username from the container
119 # Attempting to retrieve username from the container
117 username = get_container_username(environ, self.config)
120 username = get_container_username(environ, self.config)
118
121
119 # If not authenticated by the container, running basic auth
122 # If not authenticated by the container, running basic auth
120 if not username:
123 if not username:
121 self.authenticate.realm = \
124 self.authenticate.realm = \
122 safe_str(self.config['rhodecode_realm'])
125 safe_str(self.config['rhodecode_realm'])
123 result = self.authenticate(environ)
126 result = self.authenticate(environ)
124 if isinstance(result, str):
127 if isinstance(result, str):
125 AUTH_TYPE.update(environ, 'basic')
128 AUTH_TYPE.update(environ, 'basic')
126 REMOTE_USER.update(environ, result)
129 REMOTE_USER.update(environ, result)
127 username = result
130 username = result
128 else:
131 else:
129 return result.wsgi_application(environ, start_response)
132 return result.wsgi_application(environ, start_response)
130
133
131 #==============================================================
134 #==============================================================
132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
135 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
133 #==============================================================
136 #==============================================================
134 try:
137 try:
135 user = self.__get_user(username)
138 user = self.__get_user(username)
136 if user is None or not user.active:
139 if user is None or not user.active:
137 return HTTPForbidden()(environ, start_response)
140 return HTTPForbidden()(environ, start_response)
138 username = user.username
141 username = user.username
139 except:
142 except:
140 log.error(traceback.format_exc())
143 log.error(traceback.format_exc())
141 return HTTPInternalServerError()(environ, start_response)
144 return HTTPInternalServerError()(environ, start_response)
142
145
143 #check permissions for this repository
146 #check permissions for this repository
144 perm = self._check_permission(action, user, repo_name)
147 perm = self._check_permission(action, user, repo_name)
145 if perm is not True:
148 if perm is not True:
146 return HTTPForbidden()(environ, start_response)
149 return HTTPForbidden()(environ, start_response)
147
150
148 # extras are injected into mercurial UI object and later available
151 # extras are injected into mercurial UI object and later available
149 # in hg hooks executed by rhodecode
152 # in hg hooks executed by rhodecode
150 extras = {
153 extras = {
151 'ip': ipaddr,
154 'ip': ipaddr,
152 'username': username,
155 'username': username,
153 'action': action,
156 'action': action,
154 'repository': repo_name,
157 'repository': repo_name,
155 'scm': 'hg',
158 'scm': 'hg',
156 }
159 }
157
160
158 #======================================================================
161 #======================================================================
159 # MERCURIAL REQUEST HANDLING
162 # MERCURIAL REQUEST HANDLING
160 #======================================================================
163 #======================================================================
161 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
164 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
162 log.debug('Repository path is %s' % repo_path)
165 log.debug('Repository path is %s' % repo_path)
163
166
164 baseui = make_ui('db')
167 baseui = make_ui('db')
165 self.__inject_extras(repo_path, baseui, extras)
168 self.__inject_extras(repo_path, baseui, extras)
166
169
167 try:
170 try:
168 # invalidate cache on push
171 # invalidate cache on push
169 if action == 'push':
172 if action == 'push':
170 self._invalidate_cache(repo_name)
173 self._invalidate_cache(repo_name)
171 log.info('%s action on HG repo "%s"' % (action, repo_name))
174 log.info('%s action on HG repo "%s"' % (action, repo_name))
172 app = self.__make_app(repo_path, baseui, extras)
175 app = self.__make_app(repo_path, baseui, extras)
173 return app(environ, start_response)
176 return app(environ, start_response)
174 except RepoError, e:
177 except RepoError, e:
175 if str(e).find('not found') != -1:
178 if str(e).find('not found') != -1:
176 return HTTPNotFound()(environ, start_response)
179 return HTTPNotFound()(environ, start_response)
177 except Exception:
180 except Exception:
178 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
179 return HTTPInternalServerError()(environ, start_response)
182 return HTTPInternalServerError()(environ, start_response)
180
183
181 def __make_app(self, repo_name, baseui, extras):
184 def __make_app(self, repo_name, baseui, extras):
182 """
185 """
183 Make an wsgi application using hgweb, and inject generated baseui
186 Make an wsgi application using hgweb, and inject generated baseui
184 instance, additionally inject some extras into ui object
187 instance, additionally inject some extras into ui object
185 """
188 """
186 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
187
190
188 def __get_repository(self, environ):
191 def __get_repository(self, environ):
189 """
192 """
190 Get's repository name out of PATH_INFO header
193 Get's repository name out of PATH_INFO header
191
194
192 :param environ: environ where PATH_INFO is stored
195 :param environ: environ where PATH_INFO is stored
193 """
196 """
194 try:
197 try:
195 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
198 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
196 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
199 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
197 if repo_name.endswith('/'):
200 if repo_name.endswith('/'):
198 repo_name = repo_name.rstrip('/')
201 repo_name = repo_name.rstrip('/')
199 except:
202 except:
200 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
201 raise
204 raise
202
205
203 return repo_name
206 return repo_name
204
207
205 def __get_user(self, username):
208 def __get_user(self, username):
206 return User.get_by_username(username)
209 return User.get_by_username(username)
207
210
208 def __get_action(self, environ):
211 def __get_action(self, environ):
209 """
212 """
210 Maps mercurial request commands into a clone,pull or push command.
213 Maps mercurial request commands into a clone,pull or push command.
211 This should always return a valid command string
214 This should always return a valid command string
212
215
213 :param environ:
216 :param environ:
214 """
217 """
215 mapping = {'changegroup': 'pull',
218 mapping = {'changegroup': 'pull',
216 'changegroupsubset': 'pull',
219 'changegroupsubset': 'pull',
217 'stream_out': 'pull',
220 'stream_out': 'pull',
218 'listkeys': 'pull',
221 'listkeys': 'pull',
219 'unbundle': 'push',
222 'unbundle': 'push',
220 'pushkey': 'push', }
223 'pushkey': 'push', }
221 for qry in environ['QUERY_STRING'].split('&'):
224 for qry in environ['QUERY_STRING'].split('&'):
222 if qry.startswith('cmd'):
225 if qry.startswith('cmd'):
223 cmd = qry.split('=')[-1]
226 cmd = qry.split('=')[-1]
224 if cmd in mapping:
227 if cmd in mapping:
225 return mapping[cmd]
228 return mapping[cmd]
226
229
227 return 'pull'
230 return 'pull'
228
231
229 raise Exception('Unable to detect pull/push action !!'
232 raise Exception('Unable to detect pull/push action !!'
230 'Are you using non standard command or client ?')
233 'Are you using non standard command or client ?')
231
234
232 def __inject_extras(self, repo_path, baseui, extras={}):
235 def __inject_extras(self, repo_path, baseui, extras={}):
233 """
236 """
234 Injects some extra params into baseui instance
237 Injects some extra params into baseui instance
235
238
236 also overwrites global settings with those takes from local hgrc file
239 also overwrites global settings with those takes from local hgrc file
237
240
238 :param baseui: baseui instance
241 :param baseui: baseui instance
239 :param extras: dict with extra params to put into baseui
242 :param extras: dict with extra params to put into baseui
240 """
243 """
241
244
242 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
245 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
243
246
244 # make our hgweb quiet so it doesn't print output
247 # make our hgweb quiet so it doesn't print output
245 baseui.setconfig('ui', 'quiet', 'true')
248 baseui.setconfig('ui', 'quiet', 'true')
246
249
247 #inject some additional parameters that will be available in ui
250 #inject some additional parameters that will be available in ui
248 #for hooks
251 #for hooks
249 for k, v in extras.items():
252 for k, v in extras.items():
250 baseui.setconfig('rhodecode_extras', k, v)
253 baseui.setconfig('rhodecode_extras', k, v)
251
254
252 repoui = make_ui('file', hgrc, False)
255 repoui = make_ui('file', hgrc, False)
253
256
254 if repoui:
257 if repoui:
255 #overwrite our ui instance with the section from hgrc file
258 #overwrite our ui instance with the section from hgrc file
256 for section in ui_sections:
259 for section in ui_sections:
257 for k, v in repoui.configitems(section):
260 for k, v in repoui.configitems(section):
258 baseui.setconfig(section, k, v)
261 baseui.setconfig(section, k, v)
@@ -1,703 +1,703 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 from os.path import abspath
35 from os.path import abspath
36 from os.path import dirname as dn, join as jn
36 from os.path import dirname as dn, join as jn
37
37
38 from paste.script.command import Command, BadCommand
38 from paste.script.command import Command, BadCommand
39
39
40 from mercurial import ui, config
40 from mercurial import ui, config
41
41
42 from webhelpers.text import collapse, remove_formatting, strip_tags
42 from webhelpers.text import collapse, remove_formatting, strip_tags
43
43
44 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs import get_backend
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
45 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.utils.helpers import get_scm
47 from rhodecode.lib.vcs.utils.helpers import get_scm
48 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
49
49
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
54 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
55 from rhodecode.model.meta import Session
55 from rhodecode.model.meta import Session
56 from rhodecode.model.repos_group import ReposGroupModel
56 from rhodecode.model.repos_group import ReposGroupModel
57 from rhodecode.lib.utils2 import safe_str, safe_unicode
57 from rhodecode.lib.utils2 import safe_str, safe_unicode
58 from rhodecode.lib.vcs.utils.fakemod import create_module
58 from rhodecode.lib.vcs.utils.fakemod import create_module
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
62 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
63
63
64
64
65 def recursive_replace(str_, replace=' '):
65 def recursive_replace(str_, replace=' '):
66 """
66 """
67 Recursive replace of given sign to just one instance
67 Recursive replace of given sign to just one instance
68
68
69 :param str_: given string
69 :param str_: given string
70 :param replace: char to find and replace multiple instances
70 :param replace: char to find and replace multiple instances
71
71
72 Examples::
72 Examples::
73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
73 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
74 'Mighty-Mighty-Bo-sstones'
74 'Mighty-Mighty-Bo-sstones'
75 """
75 """
76
76
77 if str_.find(replace * 2) == -1:
77 if str_.find(replace * 2) == -1:
78 return str_
78 return str_
79 else:
79 else:
80 str_ = str_.replace(replace * 2, replace)
80 str_ = str_.replace(replace * 2, replace)
81 return recursive_replace(str_, replace)
81 return recursive_replace(str_, replace)
82
82
83
83
84 def repo_name_slug(value):
84 def repo_name_slug(value):
85 """
85 """
86 Return slug of name of repository
86 Return slug of name of repository
87 This function is called on each creation/modification
87 This function is called on each creation/modification
88 of repository to prevent bad names in repo
88 of repository to prevent bad names in repo
89 """
89 """
90
90
91 slug = remove_formatting(value)
91 slug = remove_formatting(value)
92 slug = strip_tags(slug)
92 slug = strip_tags(slug)
93
93
94 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
94 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
95 slug = slug.replace(c, '-')
95 slug = slug.replace(c, '-')
96 slug = recursive_replace(slug, '-')
96 slug = recursive_replace(slug, '-')
97 slug = collapse(slug, '-')
97 slug = collapse(slug, '-')
98 return slug
98 return slug
99
99
100
100
101 def get_repo_slug(request):
101 def get_repo_slug(request):
102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
102 _repo = request.environ['pylons.routes_dict'].get('repo_name')
103 if _repo:
103 if _repo:
104 _repo = _repo.rstrip('/')
104 _repo = _repo.rstrip('/')
105 return _repo
105 return _repo
106
106
107
107
108 def get_repos_group_slug(request):
108 def get_repos_group_slug(request):
109 _group = request.environ['pylons.routes_dict'].get('group_name')
109 _group = request.environ['pylons.routes_dict'].get('group_name')
110 if _group:
110 if _group:
111 _group = _group.rstrip('/')
111 _group = _group.rstrip('/')
112 return _group
112 return _group
113
113
114
114
115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
115 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
116 """
116 """
117 Action logger for various actions made by users
117 Action logger for various actions made by users
118
118
119 :param user: user that made this action, can be a unique username string or
119 :param user: user that made this action, can be a unique username string or
120 object containing user_id attribute
120 object containing user_id attribute
121 :param action: action to log, should be on of predefined unique actions for
121 :param action: action to log, should be on of predefined unique actions for
122 easy translations
122 easy translations
123 :param repo: string name of repository or object containing repo_id,
123 :param repo: string name of repository or object containing repo_id,
124 that action was made on
124 that action was made on
125 :param ipaddr: optional ip address from what the action was made
125 :param ipaddr: optional ip address from what the action was made
126 :param sa: optional sqlalchemy session
126 :param sa: optional sqlalchemy session
127
127
128 """
128 """
129
129
130 if not sa:
130 if not sa:
131 sa = meta.Session()
131 sa = meta.Session()
132
132
133 try:
133 try:
134 if hasattr(user, 'user_id'):
134 if hasattr(user, 'user_id'):
135 user_obj = user
135 user_obj = user
136 elif isinstance(user, basestring):
136 elif isinstance(user, basestring):
137 user_obj = User.get_by_username(user)
137 user_obj = User.get_by_username(user)
138 else:
138 else:
139 raise Exception('You have to provide user object or username')
139 raise Exception('You have to provide user object or username')
140
140
141 if hasattr(repo, 'repo_id'):
141 if hasattr(repo, 'repo_id'):
142 repo_obj = Repository.get(repo.repo_id)
142 repo_obj = Repository.get(repo.repo_id)
143 repo_name = repo_obj.repo_name
143 repo_name = repo_obj.repo_name
144 elif isinstance(repo, basestring):
144 elif isinstance(repo, basestring):
145 repo_name = repo.lstrip('/')
145 repo_name = repo.lstrip('/')
146 repo_obj = Repository.get_by_repo_name(repo_name)
146 repo_obj = Repository.get_by_repo_name(repo_name)
147 else:
147 else:
148 repo_obj = None
148 repo_obj = None
149 repo_name = ''
149 repo_name = ''
150
150
151 user_log = UserLog()
151 user_log = UserLog()
152 user_log.user_id = user_obj.user_id
152 user_log.user_id = user_obj.user_id
153 user_log.action = safe_unicode(action)
153 user_log.action = safe_unicode(action)
154
154
155 user_log.repository = repo_obj
155 user_log.repository = repo_obj
156 user_log.repository_name = repo_name
156 user_log.repository_name = repo_name
157
157
158 user_log.action_date = datetime.datetime.now()
158 user_log.action_date = datetime.datetime.now()
159 user_log.user_ip = ipaddr
159 user_log.user_ip = ipaddr
160 sa.add(user_log)
160 sa.add(user_log)
161
161
162 log.info(
162 log.info(
163 'Adding user %s, action %s on %s' % (user_obj, action,
163 'Adding user %s, action %s on %s' % (user_obj, action,
164 safe_unicode(repo))
164 safe_unicode(repo))
165 )
165 )
166 if commit:
166 if commit:
167 sa.commit()
167 sa.commit()
168 except:
168 except:
169 log.error(traceback.format_exc())
169 log.error(traceback.format_exc())
170 raise
170 raise
171
171
172
172
173 def get_repos(path, recursive=False):
173 def get_repos(path, recursive=False):
174 """
174 """
175 Scans given path for repos and return (name,(type,path)) tuple
175 Scans given path for repos and return (name,(type,path)) tuple
176
176
177 :param path: path to scan for repositories
177 :param path: path to scan for repositories
178 :param recursive: recursive search and return names with subdirs in front
178 :param recursive: recursive search and return names with subdirs in front
179 """
179 """
180
180
181 # remove ending slash for better results
181 # remove ending slash for better results
182 path = path.rstrip(os.sep)
182 path = path.rstrip(os.sep)
183
183
184 def _get_repos(p):
184 def _get_repos(p):
185 if not os.access(p, os.W_OK):
185 if not os.access(p, os.W_OK):
186 return
186 return
187 for dirpath in os.listdir(p):
187 for dirpath in os.listdir(p):
188 if os.path.isfile(os.path.join(p, dirpath)):
188 if os.path.isfile(os.path.join(p, dirpath)):
189 continue
189 continue
190 cur_path = os.path.join(p, dirpath)
190 cur_path = os.path.join(p, dirpath)
191 try:
191 try:
192 scm_info = get_scm(cur_path)
192 scm_info = get_scm(cur_path)
193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
193 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
194 except VCSError:
194 except VCSError:
195 if not recursive:
195 if not recursive:
196 continue
196 continue
197 #check if this dir containts other repos for recursive scan
197 #check if this dir containts other repos for recursive scan
198 rec_path = os.path.join(p, dirpath)
198 rec_path = os.path.join(p, dirpath)
199 if os.path.isdir(rec_path):
199 if os.path.isdir(rec_path):
200 for inner_scm in _get_repos(rec_path):
200 for inner_scm in _get_repos(rec_path):
201 yield inner_scm
201 yield inner_scm
202
202
203 return _get_repos(path)
203 return _get_repos(path)
204
204
205
205
206 def is_valid_repo(repo_name, base_path):
206 def is_valid_repo(repo_name, base_path):
207 """
207 """
208 Returns True if given path is a valid repository False otherwise
208 Returns True if given path is a valid repository False otherwise
209
209
210 :param repo_name:
210 :param repo_name:
211 :param base_path:
211 :param base_path:
212
212
213 :return True: if given path is a valid repository
213 :return True: if given path is a valid repository
214 """
214 """
215 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
215 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
216
216
217 try:
217 try:
218 get_scm(full_path)
218 get_scm(full_path)
219 return True
219 return True
220 except VCSError:
220 except VCSError:
221 return False
221 return False
222
222
223
223
224 def is_valid_repos_group(repos_group_name, base_path):
224 def is_valid_repos_group(repos_group_name, base_path):
225 """
225 """
226 Returns True if given path is a repos group False otherwise
226 Returns True if given path is a repos group False otherwise
227
227
228 :param repo_name:
228 :param repo_name:
229 :param base_path:
229 :param base_path:
230 """
230 """
231 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
231 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
232
232
233 # check if it's not a repo
233 # check if it's not a repo
234 if is_valid_repo(repos_group_name, base_path):
234 if is_valid_repo(repos_group_name, base_path):
235 return False
235 return False
236
236
237 try:
237 try:
238 # we need to check bare git repos at higher level
238 # we need to check bare git repos at higher level
239 # since we might match branches/hooks/info/objects or possible
239 # since we might match branches/hooks/info/objects or possible
240 # other things inside bare git repo
240 # other things inside bare git repo
241 get_scm(os.path.dirname(full_path))
241 get_scm(os.path.dirname(full_path))
242 return False
242 return False
243 except VCSError:
243 except VCSError:
244 pass
244 pass
245
245
246 # check if it's a valid path
246 # check if it's a valid path
247 if os.path.isdir(full_path):
247 if os.path.isdir(full_path):
248 return True
248 return True
249
249
250 return False
250 return False
251
251
252
252
253 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
253 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
254 while True:
254 while True:
255 ok = raw_input(prompt)
255 ok = raw_input(prompt)
256 if ok in ('y', 'ye', 'yes'):
256 if ok in ('y', 'ye', 'yes'):
257 return True
257 return True
258 if ok in ('n', 'no', 'nop', 'nope'):
258 if ok in ('n', 'no', 'nop', 'nope'):
259 return False
259 return False
260 retries = retries - 1
260 retries = retries - 1
261 if retries < 0:
261 if retries < 0:
262 raise IOError
262 raise IOError
263 print complaint
263 print complaint
264
264
265 #propagated from mercurial documentation
265 #propagated from mercurial documentation
266 ui_sections = ['alias', 'auth',
266 ui_sections = ['alias', 'auth',
267 'decode/encode', 'defaults',
267 'decode/encode', 'defaults',
268 'diff', 'email',
268 'diff', 'email',
269 'extensions', 'format',
269 'extensions', 'format',
270 'merge-patterns', 'merge-tools',
270 'merge-patterns', 'merge-tools',
271 'hooks', 'http_proxy',
271 'hooks', 'http_proxy',
272 'smtp', 'patch',
272 'smtp', 'patch',
273 'paths', 'profiling',
273 'paths', 'profiling',
274 'server', 'trusted',
274 'server', 'trusted',
275 'ui', 'web', ]
275 'ui', 'web', ]
276
276
277
277
278 def make_ui(read_from='file', path=None, checkpaths=True):
278 def make_ui(read_from='file', path=None, checkpaths=True):
279 """
279 """
280 A function that will read python rc files or database
280 A function that will read python rc files or database
281 and make an mercurial ui object from read options
281 and make an mercurial ui object from read options
282
282
283 :param path: path to mercurial config file
283 :param path: path to mercurial config file
284 :param checkpaths: check the path
284 :param checkpaths: check the path
285 :param read_from: read from 'file' or 'db'
285 :param read_from: read from 'file' or 'db'
286 """
286 """
287
287
288 baseui = ui.ui()
288 baseui = ui.ui()
289
289
290 # clean the baseui object
290 # clean the baseui object
291 baseui._ocfg = config.config()
291 baseui._ocfg = config.config()
292 baseui._ucfg = config.config()
292 baseui._ucfg = config.config()
293 baseui._tcfg = config.config()
293 baseui._tcfg = config.config()
294
294
295 if read_from == 'file':
295 if read_from == 'file':
296 if not os.path.isfile(path):
296 if not os.path.isfile(path):
297 log.debug('hgrc file is not present at %s skipping...' % path)
297 log.debug('hgrc file is not present at %s skipping...' % path)
298 return False
298 return False
299 log.debug('reading hgrc from %s' % path)
299 log.debug('reading hgrc from %s' % path)
300 cfg = config.config()
300 cfg = config.config()
301 cfg.read(path)
301 cfg.read(path)
302 for section in ui_sections:
302 for section in ui_sections:
303 for k, v in cfg.items(section):
303 for k, v in cfg.items(section):
304 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
304 log.debug('settings ui from file[%s]%s:%s' % (section, k, v))
305 baseui.setconfig(section, k, v)
305 baseui.setconfig(section, k, v)
306
306
307 elif read_from == 'db':
307 elif read_from == 'db':
308 sa = meta.Session()
308 sa = meta.Session()
309 ret = sa.query(RhodeCodeUi)\
309 ret = sa.query(RhodeCodeUi)\
310 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
310 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
311 .all()
311 .all()
312
312
313 hg_ui = ret
313 hg_ui = ret
314 for ui_ in hg_ui:
314 for ui_ in hg_ui:
315 if ui_.ui_active:
315 if ui_.ui_active and ui_.ui_key != 'push_ssl':
316 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
316 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
317 ui_.ui_key, ui_.ui_value)
317 ui_.ui_key, ui_.ui_value)
318 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
318 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
319
319
320 meta.Session.remove()
320 meta.Session.remove()
321 return baseui
321 return baseui
322
322
323
323
324 def set_rhodecode_config(config):
324 def set_rhodecode_config(config):
325 """
325 """
326 Updates pylons config with new settings from database
326 Updates pylons config with new settings from database
327
327
328 :param config:
328 :param config:
329 """
329 """
330 hgsettings = RhodeCodeSetting.get_app_settings()
330 hgsettings = RhodeCodeSetting.get_app_settings()
331
331
332 for k, v in hgsettings.items():
332 for k, v in hgsettings.items():
333 config[k] = v
333 config[k] = v
334
334
335
335
336 def invalidate_cache(cache_key, *args):
336 def invalidate_cache(cache_key, *args):
337 """
337 """
338 Puts cache invalidation task into db for
338 Puts cache invalidation task into db for
339 further global cache invalidation
339 further global cache invalidation
340 """
340 """
341
341
342 from rhodecode.model.scm import ScmModel
342 from rhodecode.model.scm import ScmModel
343
343
344 if cache_key.startswith('get_repo_cached_'):
344 if cache_key.startswith('get_repo_cached_'):
345 name = cache_key.split('get_repo_cached_')[-1]
345 name = cache_key.split('get_repo_cached_')[-1]
346 ScmModel().mark_for_invalidation(name)
346 ScmModel().mark_for_invalidation(name)
347
347
348
348
349 class EmptyChangeset(BaseChangeset):
349 class EmptyChangeset(BaseChangeset):
350 """
350 """
351 An dummy empty changeset. It's possible to pass hash when creating
351 An dummy empty changeset. It's possible to pass hash when creating
352 an EmptyChangeset
352 an EmptyChangeset
353 """
353 """
354
354
355 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
355 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
356 alias=None):
356 alias=None):
357 self._empty_cs = cs
357 self._empty_cs = cs
358 self.revision = -1
358 self.revision = -1
359 self.message = ''
359 self.message = ''
360 self.author = ''
360 self.author = ''
361 self.date = ''
361 self.date = ''
362 self.repository = repo
362 self.repository = repo
363 self.requested_revision = requested_revision
363 self.requested_revision = requested_revision
364 self.alias = alias
364 self.alias = alias
365
365
366 @LazyProperty
366 @LazyProperty
367 def raw_id(self):
367 def raw_id(self):
368 """
368 """
369 Returns raw string identifying this changeset, useful for web
369 Returns raw string identifying this changeset, useful for web
370 representation.
370 representation.
371 """
371 """
372
372
373 return self._empty_cs
373 return self._empty_cs
374
374
375 @LazyProperty
375 @LazyProperty
376 def branch(self):
376 def branch(self):
377 return get_backend(self.alias).DEFAULT_BRANCH_NAME
377 return get_backend(self.alias).DEFAULT_BRANCH_NAME
378
378
379 @LazyProperty
379 @LazyProperty
380 def short_id(self):
380 def short_id(self):
381 return self.raw_id[:12]
381 return self.raw_id[:12]
382
382
383 def get_file_changeset(self, path):
383 def get_file_changeset(self, path):
384 return self
384 return self
385
385
386 def get_file_content(self, path):
386 def get_file_content(self, path):
387 return u''
387 return u''
388
388
389 def get_file_size(self, path):
389 def get_file_size(self, path):
390 return 0
390 return 0
391
391
392
392
393 def map_groups(path):
393 def map_groups(path):
394 """
394 """
395 Given a full path to a repository, create all nested groups that this
395 Given a full path to a repository, create all nested groups that this
396 repo is inside. This function creates parent-child relationships between
396 repo is inside. This function creates parent-child relationships between
397 groups and creates default perms for all new groups.
397 groups and creates default perms for all new groups.
398
398
399 :param paths: full path to repository
399 :param paths: full path to repository
400 """
400 """
401 sa = meta.Session()
401 sa = meta.Session()
402 groups = path.split(Repository.url_sep())
402 groups = path.split(Repository.url_sep())
403 parent = None
403 parent = None
404 group = None
404 group = None
405
405
406 # last element is repo in nested groups structure
406 # last element is repo in nested groups structure
407 groups = groups[:-1]
407 groups = groups[:-1]
408 rgm = ReposGroupModel(sa)
408 rgm = ReposGroupModel(sa)
409 for lvl, group_name in enumerate(groups):
409 for lvl, group_name in enumerate(groups):
410 group_name = '/'.join(groups[:lvl] + [group_name])
410 group_name = '/'.join(groups[:lvl] + [group_name])
411 group = RepoGroup.get_by_group_name(group_name)
411 group = RepoGroup.get_by_group_name(group_name)
412 desc = '%s group' % group_name
412 desc = '%s group' % group_name
413
413
414 # skip folders that are now removed repos
414 # skip folders that are now removed repos
415 if REMOVED_REPO_PAT.match(group_name):
415 if REMOVED_REPO_PAT.match(group_name):
416 break
416 break
417
417
418 if group is None:
418 if group is None:
419 log.debug('creating group level: %s group_name: %s' % (lvl,
419 log.debug('creating group level: %s group_name: %s' % (lvl,
420 group_name))
420 group_name))
421 group = RepoGroup(group_name, parent)
421 group = RepoGroup(group_name, parent)
422 group.group_description = desc
422 group.group_description = desc
423 sa.add(group)
423 sa.add(group)
424 rgm._create_default_perms(group)
424 rgm._create_default_perms(group)
425 sa.flush()
425 sa.flush()
426 parent = group
426 parent = group
427 return group
427 return group
428
428
429
429
430 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
430 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
431 install_git_hook=False):
431 install_git_hook=False):
432 """
432 """
433 maps all repos given in initial_repo_list, non existing repositories
433 maps all repos given in initial_repo_list, non existing repositories
434 are created, if remove_obsolete is True it also check for db entries
434 are created, if remove_obsolete is True it also check for db entries
435 that are not in initial_repo_list and removes them.
435 that are not in initial_repo_list and removes them.
436
436
437 :param initial_repo_list: list of repositories found by scanning methods
437 :param initial_repo_list: list of repositories found by scanning methods
438 :param remove_obsolete: check for obsolete entries in database
438 :param remove_obsolete: check for obsolete entries in database
439 :param install_git_hook: if this is True, also check and install githook
439 :param install_git_hook: if this is True, also check and install githook
440 for a repo if missing
440 for a repo if missing
441 """
441 """
442 from rhodecode.model.repo import RepoModel
442 from rhodecode.model.repo import RepoModel
443 from rhodecode.model.scm import ScmModel
443 from rhodecode.model.scm import ScmModel
444 sa = meta.Session()
444 sa = meta.Session()
445 rm = RepoModel()
445 rm = RepoModel()
446 user = sa.query(User).filter(User.admin == True).first()
446 user = sa.query(User).filter(User.admin == True).first()
447 if user is None:
447 if user is None:
448 raise Exception('Missing administrative account !')
448 raise Exception('Missing administrative account !')
449 added = []
449 added = []
450
450
451 for name, repo in initial_repo_list.items():
451 for name, repo in initial_repo_list.items():
452 group = map_groups(name)
452 group = map_groups(name)
453 db_repo = rm.get_by_repo_name(name)
453 db_repo = rm.get_by_repo_name(name)
454 # found repo that is on filesystem not in RhodeCode database
454 # found repo that is on filesystem not in RhodeCode database
455 if not db_repo:
455 if not db_repo:
456 log.info('repository %s not found creating now' % name)
456 log.info('repository %s not found creating now' % name)
457 added.append(name)
457 added.append(name)
458 desc = (repo.description
458 desc = (repo.description
459 if repo.description != 'unknown'
459 if repo.description != 'unknown'
460 else '%s repository' % name)
460 else '%s repository' % name)
461 new_repo = rm.create_repo(
461 new_repo = rm.create_repo(
462 repo_name=name,
462 repo_name=name,
463 repo_type=repo.alias,
463 repo_type=repo.alias,
464 description=desc,
464 description=desc,
465 repos_group=getattr(group, 'group_id', None),
465 repos_group=getattr(group, 'group_id', None),
466 owner=user,
466 owner=user,
467 just_db=True
467 just_db=True
468 )
468 )
469 # we added that repo just now, and make sure it has githook
469 # we added that repo just now, and make sure it has githook
470 # installed
470 # installed
471 if new_repo.repo_type == 'git':
471 if new_repo.repo_type == 'git':
472 ScmModel().install_git_hook(new_repo.scm_instance)
472 ScmModel().install_git_hook(new_repo.scm_instance)
473 elif install_git_hook:
473 elif install_git_hook:
474 if db_repo.repo_type == 'git':
474 if db_repo.repo_type == 'git':
475 ScmModel().install_git_hook(db_repo.scm_instance)
475 ScmModel().install_git_hook(db_repo.scm_instance)
476 sa.commit()
476 sa.commit()
477 removed = []
477 removed = []
478 if remove_obsolete:
478 if remove_obsolete:
479 # remove from database those repositories that are not in the filesystem
479 # remove from database those repositories that are not in the filesystem
480 for repo in sa.query(Repository).all():
480 for repo in sa.query(Repository).all():
481 if repo.repo_name not in initial_repo_list.keys():
481 if repo.repo_name not in initial_repo_list.keys():
482 log.debug("Removing non existing repository found in db `%s`" %
482 log.debug("Removing non existing repository found in db `%s`" %
483 repo.repo_name)
483 repo.repo_name)
484 try:
484 try:
485 sa.delete(repo)
485 sa.delete(repo)
486 sa.commit()
486 sa.commit()
487 removed.append(repo.repo_name)
487 removed.append(repo.repo_name)
488 except:
488 except:
489 #don't hold further removals on error
489 #don't hold further removals on error
490 log.error(traceback.format_exc())
490 log.error(traceback.format_exc())
491 sa.rollback()
491 sa.rollback()
492
492
493 # clear cache keys
493 # clear cache keys
494 log.debug("Clearing cache keys now...")
494 log.debug("Clearing cache keys now...")
495 CacheInvalidation.clear_cache()
495 CacheInvalidation.clear_cache()
496 sa.commit()
496 sa.commit()
497 return added, removed
497 return added, removed
498
498
499
499
500 # set cache regions for beaker so celery can utilise it
500 # set cache regions for beaker so celery can utilise it
501 def add_cache(settings):
501 def add_cache(settings):
502 cache_settings = {'regions': None}
502 cache_settings = {'regions': None}
503 for key in settings.keys():
503 for key in settings.keys():
504 for prefix in ['beaker.cache.', 'cache.']:
504 for prefix in ['beaker.cache.', 'cache.']:
505 if key.startswith(prefix):
505 if key.startswith(prefix):
506 name = key.split(prefix)[1].strip()
506 name = key.split(prefix)[1].strip()
507 cache_settings[name] = settings[key].strip()
507 cache_settings[name] = settings[key].strip()
508 if cache_settings['regions']:
508 if cache_settings['regions']:
509 for region in cache_settings['regions'].split(','):
509 for region in cache_settings['regions'].split(','):
510 region = region.strip()
510 region = region.strip()
511 region_settings = {}
511 region_settings = {}
512 for key, value in cache_settings.items():
512 for key, value in cache_settings.items():
513 if key.startswith(region):
513 if key.startswith(region):
514 region_settings[key.split('.')[1]] = value
514 region_settings[key.split('.')[1]] = value
515 region_settings['expire'] = int(region_settings.get('expire',
515 region_settings['expire'] = int(region_settings.get('expire',
516 60))
516 60))
517 region_settings.setdefault('lock_dir',
517 region_settings.setdefault('lock_dir',
518 cache_settings.get('lock_dir'))
518 cache_settings.get('lock_dir'))
519 region_settings.setdefault('data_dir',
519 region_settings.setdefault('data_dir',
520 cache_settings.get('data_dir'))
520 cache_settings.get('data_dir'))
521
521
522 if 'type' not in region_settings:
522 if 'type' not in region_settings:
523 region_settings['type'] = cache_settings.get('type',
523 region_settings['type'] = cache_settings.get('type',
524 'memory')
524 'memory')
525 beaker.cache.cache_regions[region] = region_settings
525 beaker.cache.cache_regions[region] = region_settings
526
526
527
527
528 def load_rcextensions(root_path):
528 def load_rcextensions(root_path):
529 import rhodecode
529 import rhodecode
530 from rhodecode.config import conf
530 from rhodecode.config import conf
531
531
532 path = os.path.join(root_path, 'rcextensions', '__init__.py')
532 path = os.path.join(root_path, 'rcextensions', '__init__.py')
533 if os.path.isfile(path):
533 if os.path.isfile(path):
534 rcext = create_module('rc', path)
534 rcext = create_module('rc', path)
535 EXT = rhodecode.EXTENSIONS = rcext
535 EXT = rhodecode.EXTENSIONS = rcext
536 log.debug('Found rcextensions now loading %s...' % rcext)
536 log.debug('Found rcextensions now loading %s...' % rcext)
537
537
538 # Additional mappings that are not present in the pygments lexers
538 # Additional mappings that are not present in the pygments lexers
539 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
539 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
540
540
541 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
541 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
542
542
543 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
543 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
544 log.debug('settings custom INDEX_EXTENSIONS')
544 log.debug('settings custom INDEX_EXTENSIONS')
545 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
545 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
546
546
547 #ADDITIONAL MAPPINGS
547 #ADDITIONAL MAPPINGS
548 log.debug('adding extra into INDEX_EXTENSIONS')
548 log.debug('adding extra into INDEX_EXTENSIONS')
549 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
549 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
550
550
551
551
552 #==============================================================================
552 #==============================================================================
553 # TEST FUNCTIONS AND CREATORS
553 # TEST FUNCTIONS AND CREATORS
554 #==============================================================================
554 #==============================================================================
555 def create_test_index(repo_location, config, full_index):
555 def create_test_index(repo_location, config, full_index):
556 """
556 """
557 Makes default test index
557 Makes default test index
558
558
559 :param config: test config
559 :param config: test config
560 :param full_index:
560 :param full_index:
561 """
561 """
562
562
563 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
563 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
564 from rhodecode.lib.pidlock import DaemonLock, LockHeld
564 from rhodecode.lib.pidlock import DaemonLock, LockHeld
565
565
566 repo_location = repo_location
566 repo_location = repo_location
567
567
568 index_location = os.path.join(config['app_conf']['index_dir'])
568 index_location = os.path.join(config['app_conf']['index_dir'])
569 if not os.path.exists(index_location):
569 if not os.path.exists(index_location):
570 os.makedirs(index_location)
570 os.makedirs(index_location)
571
571
572 try:
572 try:
573 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
573 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
574 WhooshIndexingDaemon(index_location=index_location,
574 WhooshIndexingDaemon(index_location=index_location,
575 repo_location=repo_location)\
575 repo_location=repo_location)\
576 .run(full_index=full_index)
576 .run(full_index=full_index)
577 l.release()
577 l.release()
578 except LockHeld:
578 except LockHeld:
579 pass
579 pass
580
580
581
581
582 def create_test_env(repos_test_path, config):
582 def create_test_env(repos_test_path, config):
583 """
583 """
584 Makes a fresh database and
584 Makes a fresh database and
585 install test repository into tmp dir
585 install test repository into tmp dir
586 """
586 """
587 from rhodecode.lib.db_manage import DbManage
587 from rhodecode.lib.db_manage import DbManage
588 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
588 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
589
589
590 # PART ONE create db
590 # PART ONE create db
591 dbconf = config['sqlalchemy.db1.url']
591 dbconf = config['sqlalchemy.db1.url']
592 log.debug('making test db %s' % dbconf)
592 log.debug('making test db %s' % dbconf)
593
593
594 # create test dir if it doesn't exist
594 # create test dir if it doesn't exist
595 if not os.path.isdir(repos_test_path):
595 if not os.path.isdir(repos_test_path):
596 log.debug('Creating testdir %s' % repos_test_path)
596 log.debug('Creating testdir %s' % repos_test_path)
597 os.makedirs(repos_test_path)
597 os.makedirs(repos_test_path)
598
598
599 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
599 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
600 tests=True)
600 tests=True)
601 dbmanage.create_tables(override=True)
601 dbmanage.create_tables(override=True)
602 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
602 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
603 dbmanage.create_default_user()
603 dbmanage.create_default_user()
604 dbmanage.admin_prompt()
604 dbmanage.admin_prompt()
605 dbmanage.create_permissions()
605 dbmanage.create_permissions()
606 dbmanage.populate_default_permissions()
606 dbmanage.populate_default_permissions()
607 Session().commit()
607 Session().commit()
608 # PART TWO make test repo
608 # PART TWO make test repo
609 log.debug('making test vcs repositories')
609 log.debug('making test vcs repositories')
610
610
611 idx_path = config['app_conf']['index_dir']
611 idx_path = config['app_conf']['index_dir']
612 data_path = config['app_conf']['cache_dir']
612 data_path = config['app_conf']['cache_dir']
613
613
614 #clean index and data
614 #clean index and data
615 if idx_path and os.path.exists(idx_path):
615 if idx_path and os.path.exists(idx_path):
616 log.debug('remove %s' % idx_path)
616 log.debug('remove %s' % idx_path)
617 shutil.rmtree(idx_path)
617 shutil.rmtree(idx_path)
618
618
619 if data_path and os.path.exists(data_path):
619 if data_path and os.path.exists(data_path):
620 log.debug('remove %s' % data_path)
620 log.debug('remove %s' % data_path)
621 shutil.rmtree(data_path)
621 shutil.rmtree(data_path)
622
622
623 #CREATE DEFAULT TEST REPOS
623 #CREATE DEFAULT TEST REPOS
624 cur_dir = dn(dn(abspath(__file__)))
624 cur_dir = dn(dn(abspath(__file__)))
625 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
625 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
626 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
626 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
627 tar.close()
627 tar.close()
628
628
629 cur_dir = dn(dn(abspath(__file__)))
629 cur_dir = dn(dn(abspath(__file__)))
630 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
630 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
631 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
631 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
632 tar.close()
632 tar.close()
633
633
634 #LOAD VCS test stuff
634 #LOAD VCS test stuff
635 from rhodecode.tests.vcs import setup_package
635 from rhodecode.tests.vcs import setup_package
636 setup_package()
636 setup_package()
637
637
638
638
639 #==============================================================================
639 #==============================================================================
640 # PASTER COMMANDS
640 # PASTER COMMANDS
641 #==============================================================================
641 #==============================================================================
642 class BasePasterCommand(Command):
642 class BasePasterCommand(Command):
643 """
643 """
644 Abstract Base Class for paster commands.
644 Abstract Base Class for paster commands.
645
645
646 The celery commands are somewhat aggressive about loading
646 The celery commands are somewhat aggressive about loading
647 celery.conf, and since our module sets the `CELERY_LOADER`
647 celery.conf, and since our module sets the `CELERY_LOADER`
648 environment variable to our loader, we have to bootstrap a bit and
648 environment variable to our loader, we have to bootstrap a bit and
649 make sure we've had a chance to load the pylons config off of the
649 make sure we've had a chance to load the pylons config off of the
650 command line, otherwise everything fails.
650 command line, otherwise everything fails.
651 """
651 """
652 min_args = 1
652 min_args = 1
653 min_args_error = "Please provide a paster config file as an argument."
653 min_args_error = "Please provide a paster config file as an argument."
654 takes_config_file = 1
654 takes_config_file = 1
655 requires_config_file = True
655 requires_config_file = True
656
656
657 def notify_msg(self, msg, log=False):
657 def notify_msg(self, msg, log=False):
658 """Make a notification to user, additionally if logger is passed
658 """Make a notification to user, additionally if logger is passed
659 it logs this action using given logger
659 it logs this action using given logger
660
660
661 :param msg: message that will be printed to user
661 :param msg: message that will be printed to user
662 :param log: logging instance, to use to additionally log this message
662 :param log: logging instance, to use to additionally log this message
663
663
664 """
664 """
665 if log and isinstance(log, logging):
665 if log and isinstance(log, logging):
666 log(msg)
666 log(msg)
667
667
668 def run(self, args):
668 def run(self, args):
669 """
669 """
670 Overrides Command.run
670 Overrides Command.run
671
671
672 Checks for a config file argument and loads it.
672 Checks for a config file argument and loads it.
673 """
673 """
674 if len(args) < self.min_args:
674 if len(args) < self.min_args:
675 raise BadCommand(
675 raise BadCommand(
676 self.min_args_error % {'min_args': self.min_args,
676 self.min_args_error % {'min_args': self.min_args,
677 'actual_args': len(args)})
677 'actual_args': len(args)})
678
678
679 # Decrement because we're going to lob off the first argument.
679 # Decrement because we're going to lob off the first argument.
680 # @@ This is hacky
680 # @@ This is hacky
681 self.min_args -= 1
681 self.min_args -= 1
682 self.bootstrap_config(args[0])
682 self.bootstrap_config(args[0])
683 self.update_parser()
683 self.update_parser()
684 return super(BasePasterCommand, self).run(args[1:])
684 return super(BasePasterCommand, self).run(args[1:])
685
685
686 def update_parser(self):
686 def update_parser(self):
687 """
687 """
688 Abstract method. Allows for the class's parser to be updated
688 Abstract method. Allows for the class's parser to be updated
689 before the superclass's `run` method is called. Necessary to
689 before the superclass's `run` method is called. Necessary to
690 allow options/arguments to be passed through to the underlying
690 allow options/arguments to be passed through to the underlying
691 celery command.
691 celery command.
692 """
692 """
693 raise NotImplementedError("Abstract Method.")
693 raise NotImplementedError("Abstract Method.")
694
694
695 def bootstrap_config(self, conf):
695 def bootstrap_config(self, conf):
696 """
696 """
697 Loads the pylons configuration.
697 Loads the pylons configuration.
698 """
698 """
699 from pylons import config as pylonsconfig
699 from pylons import config as pylonsconfig
700
700
701 self.path_to_ini_file = os.path.realpath(conf)
701 self.path_to_ini_file = os.path.realpath(conf)
702 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
702 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
703 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
703 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
@@ -1,1672 +1,1672 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 from collections import defaultdict
31 from collections import defaultdict
32
32
33 from sqlalchemy import *
33 from sqlalchemy import *
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.exc import DatabaseError
36 from sqlalchemy.exc import DatabaseError
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38 from webob.exc import HTTPNotFound
38 from webob.exc import HTTPNotFound
39
39
40 from pylons.i18n.translation import lazy_ugettext as _
40 from pylons.i18n.translation import lazy_ugettext as _
41
41
42 from rhodecode.lib.vcs import get_backend
42 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs.utils.helpers import get_scm
43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46
46
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 safe_unicode
48 safe_unicode
49 from rhodecode.lib.compat import json
49 from rhodecode.lib.compat import json
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 from rhodecode.model.meta import Base, Session
52 from rhodecode.model.meta import Base, Session
53
53
54 URL_SEP = '/'
54 URL_SEP = '/'
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 #==============================================================================
57 #==============================================================================
58 # BASE CLASSES
58 # BASE CLASSES
59 #==============================================================================
59 #==============================================================================
60
60
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62
62
63
63
64 class BaseModel(object):
64 class BaseModel(object):
65 """
65 """
66 Base Model for all classess
66 Base Model for all classess
67 """
67 """
68
68
69 @classmethod
69 @classmethod
70 def _get_keys(cls):
70 def _get_keys(cls):
71 """return column names for this model """
71 """return column names for this model """
72 return class_mapper(cls).c.keys()
72 return class_mapper(cls).c.keys()
73
73
74 def get_dict(self):
74 def get_dict(self):
75 """
75 """
76 return dict with keys and values corresponding
76 return dict with keys and values corresponding
77 to this model data """
77 to this model data """
78
78
79 d = {}
79 d = {}
80 for k in self._get_keys():
80 for k in self._get_keys():
81 d[k] = getattr(self, k)
81 d[k] = getattr(self, k)
82
82
83 # also use __json__() if present to get additional fields
83 # also use __json__() if present to get additional fields
84 _json_attr = getattr(self, '__json__', None)
84 _json_attr = getattr(self, '__json__', None)
85 if _json_attr:
85 if _json_attr:
86 # update with attributes from __json__
86 # update with attributes from __json__
87 if callable(_json_attr):
87 if callable(_json_attr):
88 _json_attr = _json_attr()
88 _json_attr = _json_attr()
89 for k, val in _json_attr.iteritems():
89 for k, val in _json_attr.iteritems():
90 d[k] = val
90 d[k] = val
91 return d
91 return d
92
92
93 def get_appstruct(self):
93 def get_appstruct(self):
94 """return list with keys and values tupples corresponding
94 """return list with keys and values tupples corresponding
95 to this model data """
95 to this model data """
96
96
97 l = []
97 l = []
98 for k in self._get_keys():
98 for k in self._get_keys():
99 l.append((k, getattr(self, k),))
99 l.append((k, getattr(self, k),))
100 return l
100 return l
101
101
102 def populate_obj(self, populate_dict):
102 def populate_obj(self, populate_dict):
103 """populate model with data from given populate_dict"""
103 """populate model with data from given populate_dict"""
104
104
105 for k in self._get_keys():
105 for k in self._get_keys():
106 if k in populate_dict:
106 if k in populate_dict:
107 setattr(self, k, populate_dict[k])
107 setattr(self, k, populate_dict[k])
108
108
109 @classmethod
109 @classmethod
110 def query(cls):
110 def query(cls):
111 return Session().query(cls)
111 return Session().query(cls)
112
112
113 @classmethod
113 @classmethod
114 def get(cls, id_):
114 def get(cls, id_):
115 if id_:
115 if id_:
116 return cls.query().get(id_)
116 return cls.query().get(id_)
117
117
118 @classmethod
118 @classmethod
119 def get_or_404(cls, id_):
119 def get_or_404(cls, id_):
120 if id_:
120 if id_:
121 res = cls.query().get(id_)
121 res = cls.query().get(id_)
122 if not res:
122 if not res:
123 raise HTTPNotFound
123 raise HTTPNotFound
124 return res
124 return res
125
125
126 @classmethod
126 @classmethod
127 def getAll(cls):
127 def getAll(cls):
128 return cls.query().all()
128 return cls.query().all()
129
129
130 @classmethod
130 @classmethod
131 def delete(cls, id_):
131 def delete(cls, id_):
132 obj = cls.query().get(id_)
132 obj = cls.query().get(id_)
133 Session().delete(obj)
133 Session().delete(obj)
134
134
135 def __repr__(self):
135 def __repr__(self):
136 if hasattr(self, '__unicode__'):
136 if hasattr(self, '__unicode__'):
137 # python repr needs to return str
137 # python repr needs to return str
138 return safe_str(self.__unicode__())
138 return safe_str(self.__unicode__())
139 return '<DB:%s>' % (self.__class__.__name__)
139 return '<DB:%s>' % (self.__class__.__name__)
140
140
141
141
142 class RhodeCodeSetting(Base, BaseModel):
142 class RhodeCodeSetting(Base, BaseModel):
143 __tablename__ = 'rhodecode_settings'
143 __tablename__ = 'rhodecode_settings'
144 __table_args__ = (
144 __table_args__ = (
145 UniqueConstraint('app_settings_name'),
145 UniqueConstraint('app_settings_name'),
146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 'mysql_charset': 'utf8'}
147 'mysql_charset': 'utf8'}
148 )
148 )
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152
152
153 def __init__(self, k='', v=''):
153 def __init__(self, k='', v=''):
154 self.app_settings_name = k
154 self.app_settings_name = k
155 self.app_settings_value = v
155 self.app_settings_value = v
156
156
157 @validates('_app_settings_value')
157 @validates('_app_settings_value')
158 def validate_settings_value(self, key, val):
158 def validate_settings_value(self, key, val):
159 assert type(val) == unicode
159 assert type(val) == unicode
160 return val
160 return val
161
161
162 @hybrid_property
162 @hybrid_property
163 def app_settings_value(self):
163 def app_settings_value(self):
164 v = self._app_settings_value
164 v = self._app_settings_value
165 if self.app_settings_name == 'ldap_active':
165 if self.app_settings_name == 'ldap_active':
166 v = str2bool(v)
166 v = str2bool(v)
167 return v
167 return v
168
168
169 @app_settings_value.setter
169 @app_settings_value.setter
170 def app_settings_value(self, val):
170 def app_settings_value(self, val):
171 """
171 """
172 Setter that will always make sure we use unicode in app_settings_value
172 Setter that will always make sure we use unicode in app_settings_value
173
173
174 :param val:
174 :param val:
175 """
175 """
176 self._app_settings_value = safe_unicode(val)
176 self._app_settings_value = safe_unicode(val)
177
177
178 def __unicode__(self):
178 def __unicode__(self):
179 return u"<%s('%s:%s')>" % (
179 return u"<%s('%s:%s')>" % (
180 self.__class__.__name__,
180 self.__class__.__name__,
181 self.app_settings_name, self.app_settings_value
181 self.app_settings_name, self.app_settings_value
182 )
182 )
183
183
184 @classmethod
184 @classmethod
185 def get_by_name(cls, ldap_key):
185 def get_by_name(cls, ldap_key):
186 return cls.query()\
186 return cls.query()\
187 .filter(cls.app_settings_name == ldap_key).scalar()
187 .filter(cls.app_settings_name == ldap_key).scalar()
188
188
189 @classmethod
189 @classmethod
190 def get_app_settings(cls, cache=False):
190 def get_app_settings(cls, cache=False):
191
191
192 ret = cls.query()
192 ret = cls.query()
193
193
194 if cache:
194 if cache:
195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
196
196
197 if not ret:
197 if not ret:
198 raise Exception('Could not get application settings !')
198 raise Exception('Could not get application settings !')
199 settings = {}
199 settings = {}
200 for each in ret:
200 for each in ret:
201 settings['rhodecode_' + each.app_settings_name] = \
201 settings['rhodecode_' + each.app_settings_name] = \
202 each.app_settings_value
202 each.app_settings_value
203
203
204 return settings
204 return settings
205
205
206 @classmethod
206 @classmethod
207 def get_ldap_settings(cls, cache=False):
207 def get_ldap_settings(cls, cache=False):
208 ret = cls.query()\
208 ret = cls.query()\
209 .filter(cls.app_settings_name.startswith('ldap_')).all()
209 .filter(cls.app_settings_name.startswith('ldap_')).all()
210 fd = {}
210 fd = {}
211 for row in ret:
211 for row in ret:
212 fd.update({row.app_settings_name: row.app_settings_value})
212 fd.update({row.app_settings_name: row.app_settings_value})
213
213
214 return fd
214 return fd
215
215
216
216
217 class RhodeCodeUi(Base, BaseModel):
217 class RhodeCodeUi(Base, BaseModel):
218 __tablename__ = 'rhodecode_ui'
218 __tablename__ = 'rhodecode_ui'
219 __table_args__ = (
219 __table_args__ = (
220 UniqueConstraint('ui_key'),
220 UniqueConstraint('ui_key'),
221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 'mysql_charset': 'utf8'}
222 'mysql_charset': 'utf8'}
223 )
223 )
224
224
225 HOOK_UPDATE = 'changegroup.update'
225 HOOK_UPDATE = 'changegroup.update'
226 HOOK_REPO_SIZE = 'changegroup.repo_size'
226 HOOK_REPO_SIZE = 'changegroup.repo_size'
227 HOOK_PUSH = 'changegroup.push_logger'
227 HOOK_PUSH = 'changegroup.push_logger'
228 HOOK_PULL = 'preoutgoing.pull_logger'
228 HOOK_PULL = 'preoutgoing.pull_logger'
229
229
230 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
230 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
231 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
234 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
235
235
236 @classmethod
236 @classmethod
237 def get_by_key(cls, key):
237 def get_by_key(cls, key):
238 return cls.query().filter(cls.ui_key == key)
238 return cls.query().filter(cls.ui_key == key)
239
239
240 @classmethod
240 @classmethod
241 def get_builtin_hooks(cls):
241 def get_builtin_hooks(cls):
242 q = cls.query()
242 q = cls.query()
243 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
243 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
244 cls.HOOK_REPO_SIZE,
244 cls.HOOK_REPO_SIZE,
245 cls.HOOK_PUSH, cls.HOOK_PULL]))
245 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 return q.all()
246 return q.all()
247
247
248 @classmethod
248 @classmethod
249 def get_custom_hooks(cls):
249 def get_custom_hooks(cls):
250 q = cls.query()
250 q = cls.query()
251 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
251 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
252 cls.HOOK_REPO_SIZE,
252 cls.HOOK_REPO_SIZE,
253 cls.HOOK_PUSH, cls.HOOK_PULL]))
253 cls.HOOK_PUSH, cls.HOOK_PULL]))
254 q = q.filter(cls.ui_section == 'hooks')
254 q = q.filter(cls.ui_section == 'hooks')
255 return q.all()
255 return q.all()
256
256
257 @classmethod
257 @classmethod
258 def get_repos_location(cls):
258 def get_repos_location(cls):
259 return cls.get_by_key('/').one().ui_value
259 return cls.get_by_key('/').one().ui_value
260
260
261 @classmethod
261 @classmethod
262 def create_or_update_hook(cls, key, val):
262 def create_or_update_hook(cls, key, val):
263 new_ui = cls.get_by_key(key).scalar() or cls()
263 new_ui = cls.get_by_key(key).scalar() or cls()
264 new_ui.ui_section = 'hooks'
264 new_ui.ui_section = 'hooks'
265 new_ui.ui_active = True
265 new_ui.ui_active = True
266 new_ui.ui_key = key
266 new_ui.ui_key = key
267 new_ui.ui_value = val
267 new_ui.ui_value = val
268
268
269 Session().add(new_ui)
269 Session().add(new_ui)
270
270
271
271
272 class User(Base, BaseModel):
272 class User(Base, BaseModel):
273 __tablename__ = 'users'
273 __tablename__ = 'users'
274 __table_args__ = (
274 __table_args__ = (
275 UniqueConstraint('username'), UniqueConstraint('email'),
275 UniqueConstraint('username'), UniqueConstraint('email'),
276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
277 'mysql_charset': 'utf8'}
277 'mysql_charset': 'utf8'}
278 )
278 )
279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
280 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
282 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
284 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
288 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
288 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290
290
291 user_log = relationship('UserLog', cascade='all')
291 user_log = relationship('UserLog', cascade='all')
292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
293
293
294 repositories = relationship('Repository')
294 repositories = relationship('Repository')
295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
298
298
299 group_member = relationship('UsersGroupMember', cascade='all')
299 group_member = relationship('UsersGroupMember', cascade='all')
300
300
301 notifications = relationship('UserNotification', cascade='all')
301 notifications = relationship('UserNotification', cascade='all')
302 # notifications assigned to this user
302 # notifications assigned to this user
303 user_created_notifications = relationship('Notification', cascade='all')
303 user_created_notifications = relationship('Notification', cascade='all')
304 # comments created by this user
304 # comments created by this user
305 user_comments = relationship('ChangesetComment', cascade='all')
305 user_comments = relationship('ChangesetComment', cascade='all')
306 #extra emails for this user
306 #extra emails for this user
307 user_emails = relationship('UserEmailMap', cascade='all')
307 user_emails = relationship('UserEmailMap', cascade='all')
308
308
309 @hybrid_property
309 @hybrid_property
310 def email(self):
310 def email(self):
311 return self._email
311 return self._email
312
312
313 @email.setter
313 @email.setter
314 def email(self, val):
314 def email(self, val):
315 self._email = val.lower() if val else None
315 self._email = val.lower() if val else None
316
316
317 @property
317 @property
318 def emails(self):
318 def emails(self):
319 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
319 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
320 return [self.email] + [x.email for x in other]
320 return [self.email] + [x.email for x in other]
321
321
322 @property
322 @property
323 def full_name(self):
323 def full_name(self):
324 return '%s %s' % (self.name, self.lastname)
324 return '%s %s' % (self.name, self.lastname)
325
325
326 @property
326 @property
327 def full_name_or_username(self):
327 def full_name_or_username(self):
328 return ('%s %s' % (self.name, self.lastname)
328 return ('%s %s' % (self.name, self.lastname)
329 if (self.name and self.lastname) else self.username)
329 if (self.name and self.lastname) else self.username)
330
330
331 @property
331 @property
332 def full_contact(self):
332 def full_contact(self):
333 return '%s %s <%s>' % (self.name, self.lastname, self.email)
333 return '%s %s <%s>' % (self.name, self.lastname, self.email)
334
334
335 @property
335 @property
336 def short_contact(self):
336 def short_contact(self):
337 return '%s %s' % (self.name, self.lastname)
337 return '%s %s' % (self.name, self.lastname)
338
338
339 @property
339 @property
340 def is_admin(self):
340 def is_admin(self):
341 return self.admin
341 return self.admin
342
342
343 def __unicode__(self):
343 def __unicode__(self):
344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
345 self.user_id, self.username)
345 self.user_id, self.username)
346
346
347 @classmethod
347 @classmethod
348 def get_by_username(cls, username, case_insensitive=False, cache=False):
348 def get_by_username(cls, username, case_insensitive=False, cache=False):
349 if case_insensitive:
349 if case_insensitive:
350 q = cls.query().filter(cls.username.ilike(username))
350 q = cls.query().filter(cls.username.ilike(username))
351 else:
351 else:
352 q = cls.query().filter(cls.username == username)
352 q = cls.query().filter(cls.username == username)
353
353
354 if cache:
354 if cache:
355 q = q.options(FromCache(
355 q = q.options(FromCache(
356 "sql_cache_short",
356 "sql_cache_short",
357 "get_user_%s" % _hash_key(username)
357 "get_user_%s" % _hash_key(username)
358 )
358 )
359 )
359 )
360 return q.scalar()
360 return q.scalar()
361
361
362 @classmethod
362 @classmethod
363 def get_by_api_key(cls, api_key, cache=False):
363 def get_by_api_key(cls, api_key, cache=False):
364 q = cls.query().filter(cls.api_key == api_key)
364 q = cls.query().filter(cls.api_key == api_key)
365
365
366 if cache:
366 if cache:
367 q = q.options(FromCache("sql_cache_short",
367 q = q.options(FromCache("sql_cache_short",
368 "get_api_key_%s" % api_key))
368 "get_api_key_%s" % api_key))
369 return q.scalar()
369 return q.scalar()
370
370
371 @classmethod
371 @classmethod
372 def get_by_email(cls, email, case_insensitive=False, cache=False):
372 def get_by_email(cls, email, case_insensitive=False, cache=False):
373 if case_insensitive:
373 if case_insensitive:
374 q = cls.query().filter(cls.email.ilike(email))
374 q = cls.query().filter(cls.email.ilike(email))
375 else:
375 else:
376 q = cls.query().filter(cls.email == email)
376 q = cls.query().filter(cls.email == email)
377
377
378 if cache:
378 if cache:
379 q = q.options(FromCache("sql_cache_short",
379 q = q.options(FromCache("sql_cache_short",
380 "get_email_key_%s" % email))
380 "get_email_key_%s" % email))
381
381
382 ret = q.scalar()
382 ret = q.scalar()
383 if ret is None:
383 if ret is None:
384 q = UserEmailMap.query()
384 q = UserEmailMap.query()
385 # try fetching in alternate email map
385 # try fetching in alternate email map
386 if case_insensitive:
386 if case_insensitive:
387 q = q.filter(UserEmailMap.email.ilike(email))
387 q = q.filter(UserEmailMap.email.ilike(email))
388 else:
388 else:
389 q = q.filter(UserEmailMap.email == email)
389 q = q.filter(UserEmailMap.email == email)
390 q = q.options(joinedload(UserEmailMap.user))
390 q = q.options(joinedload(UserEmailMap.user))
391 if cache:
391 if cache:
392 q = q.options(FromCache("sql_cache_short",
392 q = q.options(FromCache("sql_cache_short",
393 "get_email_map_key_%s" % email))
393 "get_email_map_key_%s" % email))
394 ret = getattr(q.scalar(), 'user', None)
394 ret = getattr(q.scalar(), 'user', None)
395
395
396 return ret
396 return ret
397
397
398 def update_lastlogin(self):
398 def update_lastlogin(self):
399 """Update user lastlogin"""
399 """Update user lastlogin"""
400 self.last_login = datetime.datetime.now()
400 self.last_login = datetime.datetime.now()
401 Session().add(self)
401 Session().add(self)
402 log.debug('updated user %s lastlogin' % self.username)
402 log.debug('updated user %s lastlogin' % self.username)
403
403
404 def get_api_data(self):
404 def get_api_data(self):
405 """
405 """
406 Common function for generating user related data for API
406 Common function for generating user related data for API
407 """
407 """
408 user = self
408 user = self
409 data = dict(
409 data = dict(
410 user_id=user.user_id,
410 user_id=user.user_id,
411 username=user.username,
411 username=user.username,
412 firstname=user.name,
412 firstname=user.name,
413 lastname=user.lastname,
413 lastname=user.lastname,
414 email=user.email,
414 email=user.email,
415 emails=user.emails,
415 emails=user.emails,
416 api_key=user.api_key,
416 api_key=user.api_key,
417 active=user.active,
417 active=user.active,
418 admin=user.admin,
418 admin=user.admin,
419 ldap_dn=user.ldap_dn,
419 ldap_dn=user.ldap_dn,
420 last_login=user.last_login,
420 last_login=user.last_login,
421 )
421 )
422 return data
422 return data
423
423
424 def __json__(self):
424 def __json__(self):
425 data = dict(
425 data = dict(
426 full_name=self.full_name,
426 full_name=self.full_name,
427 full_name_or_username=self.full_name_or_username,
427 full_name_or_username=self.full_name_or_username,
428 short_contact=self.short_contact,
428 short_contact=self.short_contact,
429 full_contact=self.full_contact
429 full_contact=self.full_contact
430 )
430 )
431 data.update(self.get_api_data())
431 data.update(self.get_api_data())
432 return data
432 return data
433
433
434
434
435 class UserEmailMap(Base, BaseModel):
435 class UserEmailMap(Base, BaseModel):
436 __tablename__ = 'user_email_map'
436 __tablename__ = 'user_email_map'
437 __table_args__ = (
437 __table_args__ = (
438 Index('uem_email_idx', 'email'),
438 Index('uem_email_idx', 'email'),
439 UniqueConstraint('email'),
439 UniqueConstraint('email'),
440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
441 'mysql_charset': 'utf8'}
441 'mysql_charset': 'utf8'}
442 )
442 )
443 __mapper_args__ = {}
443 __mapper_args__ = {}
444
444
445 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
445 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
447 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
447 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
448
448
449 user = relationship('User', lazy='joined')
449 user = relationship('User', lazy='joined')
450
450
451 @validates('_email')
451 @validates('_email')
452 def validate_email(self, key, email):
452 def validate_email(self, key, email):
453 # check if this email is not main one
453 # check if this email is not main one
454 main_email = Session().query(User).filter(User.email == email).scalar()
454 main_email = Session().query(User).filter(User.email == email).scalar()
455 if main_email is not None:
455 if main_email is not None:
456 raise AttributeError('email %s is present is user table' % email)
456 raise AttributeError('email %s is present is user table' % email)
457 return email
457 return email
458
458
459 @hybrid_property
459 @hybrid_property
460 def email(self):
460 def email(self):
461 return self._email
461 return self._email
462
462
463 @email.setter
463 @email.setter
464 def email(self, val):
464 def email(self, val):
465 self._email = val.lower() if val else None
465 self._email = val.lower() if val else None
466
466
467
467
468 class UserLog(Base, BaseModel):
468 class UserLog(Base, BaseModel):
469 __tablename__ = 'user_logs'
469 __tablename__ = 'user_logs'
470 __table_args__ = (
470 __table_args__ = (
471 {'extend_existing': True, 'mysql_engine': 'InnoDB',
471 {'extend_existing': True, 'mysql_engine': 'InnoDB',
472 'mysql_charset': 'utf8'},
472 'mysql_charset': 'utf8'},
473 )
473 )
474 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
474 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
476 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
476 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
477 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
477 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
480 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
480 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
481
481
482 @property
482 @property
483 def action_as_day(self):
483 def action_as_day(self):
484 return datetime.date(*self.action_date.timetuple()[:3])
484 return datetime.date(*self.action_date.timetuple()[:3])
485
485
486 user = relationship('User')
486 user = relationship('User')
487 repository = relationship('Repository', cascade='')
487 repository = relationship('Repository', cascade='')
488
488
489
489
490 class UsersGroup(Base, BaseModel):
490 class UsersGroup(Base, BaseModel):
491 __tablename__ = 'users_groups'
491 __tablename__ = 'users_groups'
492 __table_args__ = (
492 __table_args__ = (
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 'mysql_charset': 'utf8'},
494 'mysql_charset': 'utf8'},
495 )
495 )
496
496
497 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
497 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
498 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
499 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
499 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
500
500
501 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
501 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
502 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
502 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
503 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
503 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
504
504
505 def __unicode__(self):
505 def __unicode__(self):
506 return u'<userGroup(%s)>' % (self.users_group_name)
506 return u'<userGroup(%s)>' % (self.users_group_name)
507
507
508 @classmethod
508 @classmethod
509 def get_by_group_name(cls, group_name, cache=False,
509 def get_by_group_name(cls, group_name, cache=False,
510 case_insensitive=False):
510 case_insensitive=False):
511 if case_insensitive:
511 if case_insensitive:
512 q = cls.query().filter(cls.users_group_name.ilike(group_name))
512 q = cls.query().filter(cls.users_group_name.ilike(group_name))
513 else:
513 else:
514 q = cls.query().filter(cls.users_group_name == group_name)
514 q = cls.query().filter(cls.users_group_name == group_name)
515 if cache:
515 if cache:
516 q = q.options(FromCache(
516 q = q.options(FromCache(
517 "sql_cache_short",
517 "sql_cache_short",
518 "get_user_%s" % _hash_key(group_name)
518 "get_user_%s" % _hash_key(group_name)
519 )
519 )
520 )
520 )
521 return q.scalar()
521 return q.scalar()
522
522
523 @classmethod
523 @classmethod
524 def get(cls, users_group_id, cache=False):
524 def get(cls, users_group_id, cache=False):
525 users_group = cls.query()
525 users_group = cls.query()
526 if cache:
526 if cache:
527 users_group = users_group.options(FromCache("sql_cache_short",
527 users_group = users_group.options(FromCache("sql_cache_short",
528 "get_users_group_%s" % users_group_id))
528 "get_users_group_%s" % users_group_id))
529 return users_group.get(users_group_id)
529 return users_group.get(users_group_id)
530
530
531 def get_api_data(self):
531 def get_api_data(self):
532 users_group = self
532 users_group = self
533
533
534 data = dict(
534 data = dict(
535 users_group_id=users_group.users_group_id,
535 users_group_id=users_group.users_group_id,
536 group_name=users_group.users_group_name,
536 group_name=users_group.users_group_name,
537 active=users_group.users_group_active,
537 active=users_group.users_group_active,
538 )
538 )
539
539
540 return data
540 return data
541
541
542
542
543 class UsersGroupMember(Base, BaseModel):
543 class UsersGroupMember(Base, BaseModel):
544 __tablename__ = 'users_groups_members'
544 __tablename__ = 'users_groups_members'
545 __table_args__ = (
545 __table_args__ = (
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 'mysql_charset': 'utf8'},
547 'mysql_charset': 'utf8'},
548 )
548 )
549
549
550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
553
553
554 user = relationship('User', lazy='joined')
554 user = relationship('User', lazy='joined')
555 users_group = relationship('UsersGroup')
555 users_group = relationship('UsersGroup')
556
556
557 def __init__(self, gr_id='', u_id=''):
557 def __init__(self, gr_id='', u_id=''):
558 self.users_group_id = gr_id
558 self.users_group_id = gr_id
559 self.user_id = u_id
559 self.user_id = u_id
560
560
561
561
562 class Repository(Base, BaseModel):
562 class Repository(Base, BaseModel):
563 __tablename__ = 'repositories'
563 __tablename__ = 'repositories'
564 __table_args__ = (
564 __table_args__ = (
565 UniqueConstraint('repo_name'),
565 UniqueConstraint('repo_name'),
566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
567 'mysql_charset': 'utf8'},
567 'mysql_charset': 'utf8'},
568 )
568 )
569
569
570 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
571 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
572 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
572 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
573 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
573 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
575 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
575 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
576 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
576 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
577 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
577 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
578 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
578 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
579 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
579 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
580 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
580 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
581
581
582 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
582 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
584
584
585 user = relationship('User')
585 user = relationship('User')
586 fork = relationship('Repository', remote_side=repo_id)
586 fork = relationship('Repository', remote_side=repo_id)
587 group = relationship('RepoGroup')
587 group = relationship('RepoGroup')
588 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
588 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
589 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
589 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
590 stats = relationship('Statistics', cascade='all', uselist=False)
590 stats = relationship('Statistics', cascade='all', uselist=False)
591
591
592 followers = relationship('UserFollowing',
592 followers = relationship('UserFollowing',
593 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
593 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
594 cascade='all')
594 cascade='all')
595
595
596 logs = relationship('UserLog')
596 logs = relationship('UserLog')
597 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
597 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
598
598
599 pull_requests_org = relationship('PullRequest',
599 pull_requests_org = relationship('PullRequest',
600 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
600 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
601 cascade="all, delete, delete-orphan")
601 cascade="all, delete, delete-orphan")
602
602
603 pull_requests_other = relationship('PullRequest',
603 pull_requests_other = relationship('PullRequest',
604 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
604 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
605 cascade="all, delete, delete-orphan")
605 cascade="all, delete, delete-orphan")
606
606
607 def __unicode__(self):
607 def __unicode__(self):
608 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
608 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
609 self.repo_name)
609 self.repo_name)
610
610
611 @classmethod
611 @classmethod
612 def url_sep(cls):
612 def url_sep(cls):
613 return URL_SEP
613 return URL_SEP
614
614
615 @classmethod
615 @classmethod
616 def get_by_repo_name(cls, repo_name):
616 def get_by_repo_name(cls, repo_name):
617 q = Session().query(cls).filter(cls.repo_name == repo_name)
617 q = Session().query(cls).filter(cls.repo_name == repo_name)
618 q = q.options(joinedload(Repository.fork))\
618 q = q.options(joinedload(Repository.fork))\
619 .options(joinedload(Repository.user))\
619 .options(joinedload(Repository.user))\
620 .options(joinedload(Repository.group))
620 .options(joinedload(Repository.group))
621 return q.scalar()
621 return q.scalar()
622
622
623 @classmethod
623 @classmethod
624 def get_by_full_path(cls, repo_full_path):
624 def get_by_full_path(cls, repo_full_path):
625 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
625 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
626 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
626 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
627
627
628 @classmethod
628 @classmethod
629 def get_repo_forks(cls, repo_id):
629 def get_repo_forks(cls, repo_id):
630 return cls.query().filter(Repository.fork_id == repo_id)
630 return cls.query().filter(Repository.fork_id == repo_id)
631
631
632 @classmethod
632 @classmethod
633 def base_path(cls):
633 def base_path(cls):
634 """
634 """
635 Returns base path when all repos are stored
635 Returns base path when all repos are stored
636
636
637 :param cls:
637 :param cls:
638 """
638 """
639 q = Session().query(RhodeCodeUi)\
639 q = Session().query(RhodeCodeUi)\
640 .filter(RhodeCodeUi.ui_key == cls.url_sep())
640 .filter(RhodeCodeUi.ui_key == cls.url_sep())
641 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
641 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
642 return q.one().ui_value
642 return q.one().ui_value
643
643
644 @property
644 @property
645 def forks(self):
645 def forks(self):
646 """
646 """
647 Return forks of this repo
647 Return forks of this repo
648 """
648 """
649 return Repository.get_repo_forks(self.repo_id)
649 return Repository.get_repo_forks(self.repo_id)
650
650
651 @property
651 @property
652 def parent(self):
652 def parent(self):
653 """
653 """
654 Returns fork parent
654 Returns fork parent
655 """
655 """
656 return self.fork
656 return self.fork
657
657
658 @property
658 @property
659 def just_name(self):
659 def just_name(self):
660 return self.repo_name.split(Repository.url_sep())[-1]
660 return self.repo_name.split(Repository.url_sep())[-1]
661
661
662 @property
662 @property
663 def groups_with_parents(self):
663 def groups_with_parents(self):
664 groups = []
664 groups = []
665 if self.group is None:
665 if self.group is None:
666 return groups
666 return groups
667
667
668 cur_gr = self.group
668 cur_gr = self.group
669 groups.insert(0, cur_gr)
669 groups.insert(0, cur_gr)
670 while 1:
670 while 1:
671 gr = getattr(cur_gr, 'parent_group', None)
671 gr = getattr(cur_gr, 'parent_group', None)
672 cur_gr = cur_gr.parent_group
672 cur_gr = cur_gr.parent_group
673 if gr is None:
673 if gr is None:
674 break
674 break
675 groups.insert(0, gr)
675 groups.insert(0, gr)
676
676
677 return groups
677 return groups
678
678
679 @property
679 @property
680 def groups_and_repo(self):
680 def groups_and_repo(self):
681 return self.groups_with_parents, self.just_name
681 return self.groups_with_parents, self.just_name
682
682
683 @LazyProperty
683 @LazyProperty
684 def repo_path(self):
684 def repo_path(self):
685 """
685 """
686 Returns base full path for that repository means where it actually
686 Returns base full path for that repository means where it actually
687 exists on a filesystem
687 exists on a filesystem
688 """
688 """
689 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
689 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
690 Repository.url_sep())
690 Repository.url_sep())
691 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
691 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
692 return q.one().ui_value
692 return q.one().ui_value
693
693
694 @property
694 @property
695 def repo_full_path(self):
695 def repo_full_path(self):
696 p = [self.repo_path]
696 p = [self.repo_path]
697 # we need to split the name by / since this is how we store the
697 # we need to split the name by / since this is how we store the
698 # names in the database, but that eventually needs to be converted
698 # names in the database, but that eventually needs to be converted
699 # into a valid system path
699 # into a valid system path
700 p += self.repo_name.split(Repository.url_sep())
700 p += self.repo_name.split(Repository.url_sep())
701 return os.path.join(*p)
701 return os.path.join(*p)
702
702
703 def get_new_name(self, repo_name):
703 def get_new_name(self, repo_name):
704 """
704 """
705 returns new full repository name based on assigned group and new new
705 returns new full repository name based on assigned group and new new
706
706
707 :param group_name:
707 :param group_name:
708 """
708 """
709 path_prefix = self.group.full_path_splitted if self.group else []
709 path_prefix = self.group.full_path_splitted if self.group else []
710 return Repository.url_sep().join(path_prefix + [repo_name])
710 return Repository.url_sep().join(path_prefix + [repo_name])
711
711
712 @property
712 @property
713 def _ui(self):
713 def _ui(self):
714 """
714 """
715 Creates an db based ui object for this repository
715 Creates an db based ui object for this repository
716 """
716 """
717 from mercurial import ui
717 from mercurial import ui
718 from mercurial import config
718 from mercurial import config
719 baseui = ui.ui()
719 baseui = ui.ui()
720
720
721 #clean the baseui object
721 #clean the baseui object
722 baseui._ocfg = config.config()
722 baseui._ocfg = config.config()
723 baseui._ucfg = config.config()
723 baseui._ucfg = config.config()
724 baseui._tcfg = config.config()
724 baseui._tcfg = config.config()
725
725
726 ret = RhodeCodeUi.query()\
726 ret = RhodeCodeUi.query()\
727 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
727 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
728
728
729 hg_ui = ret
729 hg_ui = ret
730 for ui_ in hg_ui:
730 for ui_ in hg_ui:
731 if ui_.ui_active:
731 if ui_.ui_active and ui_.ui_key != 'push_ssl':
732 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
732 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
733 ui_.ui_key, ui_.ui_value)
733 ui_.ui_key, ui_.ui_value)
734 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
734 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
735
735
736 return baseui
736 return baseui
737
737
738 @classmethod
738 @classmethod
739 def inject_ui(cls, repo, extras={}):
739 def inject_ui(cls, repo, extras={}):
740 from rhodecode.lib.vcs.backends.hg import MercurialRepository
740 from rhodecode.lib.vcs.backends.hg import MercurialRepository
741 from rhodecode.lib.vcs.backends.git import GitRepository
741 from rhodecode.lib.vcs.backends.git import GitRepository
742 required = (MercurialRepository, GitRepository)
742 required = (MercurialRepository, GitRepository)
743 if not isinstance(repo, required):
743 if not isinstance(repo, required):
744 raise Exception('repo must be instance of %s' % required)
744 raise Exception('repo must be instance of %s' % required)
745
745
746 # inject ui extra param to log this action via push logger
746 # inject ui extra param to log this action via push logger
747 for k, v in extras.items():
747 for k, v in extras.items():
748 repo._repo.ui.setconfig('rhodecode_extras', k, v)
748 repo._repo.ui.setconfig('rhodecode_extras', k, v)
749
749
750 @classmethod
750 @classmethod
751 def is_valid(cls, repo_name):
751 def is_valid(cls, repo_name):
752 """
752 """
753 returns True if given repo name is a valid filesystem repository
753 returns True if given repo name is a valid filesystem repository
754
754
755 :param cls:
755 :param cls:
756 :param repo_name:
756 :param repo_name:
757 """
757 """
758 from rhodecode.lib.utils import is_valid_repo
758 from rhodecode.lib.utils import is_valid_repo
759
759
760 return is_valid_repo(repo_name, cls.base_path())
760 return is_valid_repo(repo_name, cls.base_path())
761
761
762 def get_api_data(self):
762 def get_api_data(self):
763 """
763 """
764 Common function for generating repo api data
764 Common function for generating repo api data
765
765
766 """
766 """
767 repo = self
767 repo = self
768 data = dict(
768 data = dict(
769 repo_id=repo.repo_id,
769 repo_id=repo.repo_id,
770 repo_name=repo.repo_name,
770 repo_name=repo.repo_name,
771 repo_type=repo.repo_type,
771 repo_type=repo.repo_type,
772 clone_uri=repo.clone_uri,
772 clone_uri=repo.clone_uri,
773 private=repo.private,
773 private=repo.private,
774 created_on=repo.created_on,
774 created_on=repo.created_on,
775 description=repo.description,
775 description=repo.description,
776 landing_rev=repo.landing_rev,
776 landing_rev=repo.landing_rev,
777 owner=repo.user.username,
777 owner=repo.user.username,
778 fork_of=repo.fork.repo_name if repo.fork else None
778 fork_of=repo.fork.repo_name if repo.fork else None
779 )
779 )
780
780
781 return data
781 return data
782
782
783 #==========================================================================
783 #==========================================================================
784 # SCM PROPERTIES
784 # SCM PROPERTIES
785 #==========================================================================
785 #==========================================================================
786
786
787 def get_changeset(self, rev=None):
787 def get_changeset(self, rev=None):
788 return get_changeset_safe(self.scm_instance, rev)
788 return get_changeset_safe(self.scm_instance, rev)
789
789
790 def get_landing_changeset(self):
790 def get_landing_changeset(self):
791 """
791 """
792 Returns landing changeset, or if that doesn't exist returns the tip
792 Returns landing changeset, or if that doesn't exist returns the tip
793 """
793 """
794 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
794 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
795 return cs
795 return cs
796
796
797 @property
797 @property
798 def tip(self):
798 def tip(self):
799 return self.get_changeset('tip')
799 return self.get_changeset('tip')
800
800
801 @property
801 @property
802 def author(self):
802 def author(self):
803 return self.tip.author
803 return self.tip.author
804
804
805 @property
805 @property
806 def last_change(self):
806 def last_change(self):
807 return self.scm_instance.last_change
807 return self.scm_instance.last_change
808
808
809 def get_comments(self, revisions=None):
809 def get_comments(self, revisions=None):
810 """
810 """
811 Returns comments for this repository grouped by revisions
811 Returns comments for this repository grouped by revisions
812
812
813 :param revisions: filter query by revisions only
813 :param revisions: filter query by revisions only
814 """
814 """
815 cmts = ChangesetComment.query()\
815 cmts = ChangesetComment.query()\
816 .filter(ChangesetComment.repo == self)
816 .filter(ChangesetComment.repo == self)
817 if revisions:
817 if revisions:
818 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
818 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
819 grouped = defaultdict(list)
819 grouped = defaultdict(list)
820 for cmt in cmts.all():
820 for cmt in cmts.all():
821 grouped[cmt.revision].append(cmt)
821 grouped[cmt.revision].append(cmt)
822 return grouped
822 return grouped
823
823
824 def statuses(self, revisions=None):
824 def statuses(self, revisions=None):
825 """
825 """
826 Returns statuses for this repository
826 Returns statuses for this repository
827
827
828 :param revisions: list of revisions to get statuses for
828 :param revisions: list of revisions to get statuses for
829 :type revisions: list
829 :type revisions: list
830 """
830 """
831
831
832 statuses = ChangesetStatus.query()\
832 statuses = ChangesetStatus.query()\
833 .filter(ChangesetStatus.repo == self)\
833 .filter(ChangesetStatus.repo == self)\
834 .filter(ChangesetStatus.version == 0)
834 .filter(ChangesetStatus.version == 0)
835 if revisions:
835 if revisions:
836 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
836 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
837 grouped = {}
837 grouped = {}
838
838
839 #maybe we have open new pullrequest without a status ?
839 #maybe we have open new pullrequest without a status ?
840 stat = ChangesetStatus.STATUS_UNDER_REVIEW
840 stat = ChangesetStatus.STATUS_UNDER_REVIEW
841 status_lbl = ChangesetStatus.get_status_lbl(stat)
841 status_lbl = ChangesetStatus.get_status_lbl(stat)
842 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
842 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
843 for rev in pr.revisions:
843 for rev in pr.revisions:
844 pr_id = pr.pull_request_id
844 pr_id = pr.pull_request_id
845 pr_repo = pr.other_repo.repo_name
845 pr_repo = pr.other_repo.repo_name
846 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
846 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
847
847
848 for stat in statuses.all():
848 for stat in statuses.all():
849 pr_id = pr_repo = None
849 pr_id = pr_repo = None
850 if stat.pull_request:
850 if stat.pull_request:
851 pr_id = stat.pull_request.pull_request_id
851 pr_id = stat.pull_request.pull_request_id
852 pr_repo = stat.pull_request.other_repo.repo_name
852 pr_repo = stat.pull_request.other_repo.repo_name
853 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
853 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
854 pr_id, pr_repo]
854 pr_id, pr_repo]
855 return grouped
855 return grouped
856
856
857 #==========================================================================
857 #==========================================================================
858 # SCM CACHE INSTANCE
858 # SCM CACHE INSTANCE
859 #==========================================================================
859 #==========================================================================
860
860
861 @property
861 @property
862 def invalidate(self):
862 def invalidate(self):
863 return CacheInvalidation.invalidate(self.repo_name)
863 return CacheInvalidation.invalidate(self.repo_name)
864
864
865 def set_invalidate(self):
865 def set_invalidate(self):
866 """
866 """
867 set a cache for invalidation for this instance
867 set a cache for invalidation for this instance
868 """
868 """
869 CacheInvalidation.set_invalidate(self.repo_name)
869 CacheInvalidation.set_invalidate(self.repo_name)
870
870
871 @LazyProperty
871 @LazyProperty
872 def scm_instance(self):
872 def scm_instance(self):
873 return self.__get_instance()
873 return self.__get_instance()
874
874
875 def scm_instance_cached(self, cache_map=None):
875 def scm_instance_cached(self, cache_map=None):
876 @cache_region('long_term')
876 @cache_region('long_term')
877 def _c(repo_name):
877 def _c(repo_name):
878 return self.__get_instance()
878 return self.__get_instance()
879 rn = self.repo_name
879 rn = self.repo_name
880 log.debug('Getting cached instance of repo')
880 log.debug('Getting cached instance of repo')
881
881
882 if cache_map:
882 if cache_map:
883 # get using prefilled cache_map
883 # get using prefilled cache_map
884 invalidate_repo = cache_map[self.repo_name]
884 invalidate_repo = cache_map[self.repo_name]
885 if invalidate_repo:
885 if invalidate_repo:
886 invalidate_repo = (None if invalidate_repo.cache_active
886 invalidate_repo = (None if invalidate_repo.cache_active
887 else invalidate_repo)
887 else invalidate_repo)
888 else:
888 else:
889 # get from invalidate
889 # get from invalidate
890 invalidate_repo = self.invalidate
890 invalidate_repo = self.invalidate
891
891
892 if invalidate_repo is not None:
892 if invalidate_repo is not None:
893 region_invalidate(_c, None, rn)
893 region_invalidate(_c, None, rn)
894 # update our cache
894 # update our cache
895 CacheInvalidation.set_valid(invalidate_repo.cache_key)
895 CacheInvalidation.set_valid(invalidate_repo.cache_key)
896 return _c(rn)
896 return _c(rn)
897
897
898 def __get_instance(self):
898 def __get_instance(self):
899 repo_full_path = self.repo_full_path
899 repo_full_path = self.repo_full_path
900 try:
900 try:
901 alias = get_scm(repo_full_path)[0]
901 alias = get_scm(repo_full_path)[0]
902 log.debug('Creating instance of %s repository' % alias)
902 log.debug('Creating instance of %s repository' % alias)
903 backend = get_backend(alias)
903 backend = get_backend(alias)
904 except VCSError:
904 except VCSError:
905 log.error(traceback.format_exc())
905 log.error(traceback.format_exc())
906 log.error('Perhaps this repository is in db and not in '
906 log.error('Perhaps this repository is in db and not in '
907 'filesystem run rescan repositories with '
907 'filesystem run rescan repositories with '
908 '"destroy old data " option from admin panel')
908 '"destroy old data " option from admin panel')
909 return
909 return
910
910
911 if alias == 'hg':
911 if alias == 'hg':
912
912
913 repo = backend(safe_str(repo_full_path), create=False,
913 repo = backend(safe_str(repo_full_path), create=False,
914 baseui=self._ui)
914 baseui=self._ui)
915 # skip hidden web repository
915 # skip hidden web repository
916 if repo._get_hidden():
916 if repo._get_hidden():
917 return
917 return
918 else:
918 else:
919 repo = backend(repo_full_path, create=False)
919 repo = backend(repo_full_path, create=False)
920
920
921 return repo
921 return repo
922
922
923
923
924 class RepoGroup(Base, BaseModel):
924 class RepoGroup(Base, BaseModel):
925 __tablename__ = 'groups'
925 __tablename__ = 'groups'
926 __table_args__ = (
926 __table_args__ = (
927 UniqueConstraint('group_name', 'group_parent_id'),
927 UniqueConstraint('group_name', 'group_parent_id'),
928 CheckConstraint('group_id != group_parent_id'),
928 CheckConstraint('group_id != group_parent_id'),
929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
930 'mysql_charset': 'utf8'},
930 'mysql_charset': 'utf8'},
931 )
931 )
932 __mapper_args__ = {'order_by': 'group_name'}
932 __mapper_args__ = {'order_by': 'group_name'}
933
933
934 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
934 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
935 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
935 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
936 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
936 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
937 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
937 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
938
938
939 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
939 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
940 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
940 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
941
941
942 parent_group = relationship('RepoGroup', remote_side=group_id)
942 parent_group = relationship('RepoGroup', remote_side=group_id)
943
943
944 def __init__(self, group_name='', parent_group=None):
944 def __init__(self, group_name='', parent_group=None):
945 self.group_name = group_name
945 self.group_name = group_name
946 self.parent_group = parent_group
946 self.parent_group = parent_group
947
947
948 def __unicode__(self):
948 def __unicode__(self):
949 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
949 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
950 self.group_name)
950 self.group_name)
951
951
952 @classmethod
952 @classmethod
953 def groups_choices(cls):
953 def groups_choices(cls):
954 from webhelpers.html import literal as _literal
954 from webhelpers.html import literal as _literal
955 repo_groups = [('', '')]
955 repo_groups = [('', '')]
956 sep = ' &raquo; '
956 sep = ' &raquo; '
957 _name = lambda k: _literal(sep.join(k))
957 _name = lambda k: _literal(sep.join(k))
958
958
959 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
959 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
960 for x in cls.query().all()])
960 for x in cls.query().all()])
961
961
962 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
962 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
963 return repo_groups
963 return repo_groups
964
964
965 @classmethod
965 @classmethod
966 def url_sep(cls):
966 def url_sep(cls):
967 return URL_SEP
967 return URL_SEP
968
968
969 @classmethod
969 @classmethod
970 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
970 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
971 if case_insensitive:
971 if case_insensitive:
972 gr = cls.query()\
972 gr = cls.query()\
973 .filter(cls.group_name.ilike(group_name))
973 .filter(cls.group_name.ilike(group_name))
974 else:
974 else:
975 gr = cls.query()\
975 gr = cls.query()\
976 .filter(cls.group_name == group_name)
976 .filter(cls.group_name == group_name)
977 if cache:
977 if cache:
978 gr = gr.options(FromCache(
978 gr = gr.options(FromCache(
979 "sql_cache_short",
979 "sql_cache_short",
980 "get_group_%s" % _hash_key(group_name)
980 "get_group_%s" % _hash_key(group_name)
981 )
981 )
982 )
982 )
983 return gr.scalar()
983 return gr.scalar()
984
984
985 @property
985 @property
986 def parents(self):
986 def parents(self):
987 parents_recursion_limit = 5
987 parents_recursion_limit = 5
988 groups = []
988 groups = []
989 if self.parent_group is None:
989 if self.parent_group is None:
990 return groups
990 return groups
991 cur_gr = self.parent_group
991 cur_gr = self.parent_group
992 groups.insert(0, cur_gr)
992 groups.insert(0, cur_gr)
993 cnt = 0
993 cnt = 0
994 while 1:
994 while 1:
995 cnt += 1
995 cnt += 1
996 gr = getattr(cur_gr, 'parent_group', None)
996 gr = getattr(cur_gr, 'parent_group', None)
997 cur_gr = cur_gr.parent_group
997 cur_gr = cur_gr.parent_group
998 if gr is None:
998 if gr is None:
999 break
999 break
1000 if cnt == parents_recursion_limit:
1000 if cnt == parents_recursion_limit:
1001 # this will prevent accidental infinit loops
1001 # this will prevent accidental infinit loops
1002 log.error('group nested more than %s' %
1002 log.error('group nested more than %s' %
1003 parents_recursion_limit)
1003 parents_recursion_limit)
1004 break
1004 break
1005
1005
1006 groups.insert(0, gr)
1006 groups.insert(0, gr)
1007 return groups
1007 return groups
1008
1008
1009 @property
1009 @property
1010 def children(self):
1010 def children(self):
1011 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1011 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1012
1012
1013 @property
1013 @property
1014 def name(self):
1014 def name(self):
1015 return self.group_name.split(RepoGroup.url_sep())[-1]
1015 return self.group_name.split(RepoGroup.url_sep())[-1]
1016
1016
1017 @property
1017 @property
1018 def full_path(self):
1018 def full_path(self):
1019 return self.group_name
1019 return self.group_name
1020
1020
1021 @property
1021 @property
1022 def full_path_splitted(self):
1022 def full_path_splitted(self):
1023 return self.group_name.split(RepoGroup.url_sep())
1023 return self.group_name.split(RepoGroup.url_sep())
1024
1024
1025 @property
1025 @property
1026 def repositories(self):
1026 def repositories(self):
1027 return Repository.query()\
1027 return Repository.query()\
1028 .filter(Repository.group == self)\
1028 .filter(Repository.group == self)\
1029 .order_by(Repository.repo_name)
1029 .order_by(Repository.repo_name)
1030
1030
1031 @property
1031 @property
1032 def repositories_recursive_count(self):
1032 def repositories_recursive_count(self):
1033 cnt = self.repositories.count()
1033 cnt = self.repositories.count()
1034
1034
1035 def children_count(group):
1035 def children_count(group):
1036 cnt = 0
1036 cnt = 0
1037 for child in group.children:
1037 for child in group.children:
1038 cnt += child.repositories.count()
1038 cnt += child.repositories.count()
1039 cnt += children_count(child)
1039 cnt += children_count(child)
1040 return cnt
1040 return cnt
1041
1041
1042 return cnt + children_count(self)
1042 return cnt + children_count(self)
1043
1043
1044 def get_new_name(self, group_name):
1044 def get_new_name(self, group_name):
1045 """
1045 """
1046 returns new full group name based on parent and new name
1046 returns new full group name based on parent and new name
1047
1047
1048 :param group_name:
1048 :param group_name:
1049 """
1049 """
1050 path_prefix = (self.parent_group.full_path_splitted if
1050 path_prefix = (self.parent_group.full_path_splitted if
1051 self.parent_group else [])
1051 self.parent_group else [])
1052 return RepoGroup.url_sep().join(path_prefix + [group_name])
1052 return RepoGroup.url_sep().join(path_prefix + [group_name])
1053
1053
1054
1054
1055 class Permission(Base, BaseModel):
1055 class Permission(Base, BaseModel):
1056 __tablename__ = 'permissions'
1056 __tablename__ = 'permissions'
1057 __table_args__ = (
1057 __table_args__ = (
1058 Index('p_perm_name_idx', 'permission_name'),
1058 Index('p_perm_name_idx', 'permission_name'),
1059 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1059 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1060 'mysql_charset': 'utf8'},
1060 'mysql_charset': 'utf8'},
1061 )
1061 )
1062 PERMS = [
1062 PERMS = [
1063 ('repository.none', _('Repository no access')),
1063 ('repository.none', _('Repository no access')),
1064 ('repository.read', _('Repository read access')),
1064 ('repository.read', _('Repository read access')),
1065 ('repository.write', _('Repository write access')),
1065 ('repository.write', _('Repository write access')),
1066 ('repository.admin', _('Repository admin access')),
1066 ('repository.admin', _('Repository admin access')),
1067
1067
1068 ('group.none', _('Repositories Group no access')),
1068 ('group.none', _('Repositories Group no access')),
1069 ('group.read', _('Repositories Group read access')),
1069 ('group.read', _('Repositories Group read access')),
1070 ('group.write', _('Repositories Group write access')),
1070 ('group.write', _('Repositories Group write access')),
1071 ('group.admin', _('Repositories Group admin access')),
1071 ('group.admin', _('Repositories Group admin access')),
1072
1072
1073 ('hg.admin', _('RhodeCode Administrator')),
1073 ('hg.admin', _('RhodeCode Administrator')),
1074 ('hg.create.none', _('Repository creation disabled')),
1074 ('hg.create.none', _('Repository creation disabled')),
1075 ('hg.create.repository', _('Repository creation enabled')),
1075 ('hg.create.repository', _('Repository creation enabled')),
1076 ('hg.register.none', _('Register disabled')),
1076 ('hg.register.none', _('Register disabled')),
1077 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1077 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1078 'with manual activation')),
1078 'with manual activation')),
1079
1079
1080 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1080 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1081 'with auto activation')),
1081 'with auto activation')),
1082 ]
1082 ]
1083
1083
1084 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1084 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1085 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1085 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1086 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1086 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1087
1087
1088 def __unicode__(self):
1088 def __unicode__(self):
1089 return u"<%s('%s:%s')>" % (
1089 return u"<%s('%s:%s')>" % (
1090 self.__class__.__name__, self.permission_id, self.permission_name
1090 self.__class__.__name__, self.permission_id, self.permission_name
1091 )
1091 )
1092
1092
1093 @classmethod
1093 @classmethod
1094 def get_by_key(cls, key):
1094 def get_by_key(cls, key):
1095 return cls.query().filter(cls.permission_name == key).scalar()
1095 return cls.query().filter(cls.permission_name == key).scalar()
1096
1096
1097 @classmethod
1097 @classmethod
1098 def get_default_perms(cls, default_user_id):
1098 def get_default_perms(cls, default_user_id):
1099 q = Session().query(UserRepoToPerm, Repository, cls)\
1099 q = Session().query(UserRepoToPerm, Repository, cls)\
1100 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1100 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1101 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1101 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1102 .filter(UserRepoToPerm.user_id == default_user_id)
1102 .filter(UserRepoToPerm.user_id == default_user_id)
1103
1103
1104 return q.all()
1104 return q.all()
1105
1105
1106 @classmethod
1106 @classmethod
1107 def get_default_group_perms(cls, default_user_id):
1107 def get_default_group_perms(cls, default_user_id):
1108 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1108 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1109 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1109 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1110 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1110 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1111 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1111 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1112
1112
1113 return q.all()
1113 return q.all()
1114
1114
1115
1115
1116 class UserRepoToPerm(Base, BaseModel):
1116 class UserRepoToPerm(Base, BaseModel):
1117 __tablename__ = 'repo_to_perm'
1117 __tablename__ = 'repo_to_perm'
1118 __table_args__ = (
1118 __table_args__ = (
1119 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1119 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1120 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1121 'mysql_charset': 'utf8'}
1121 'mysql_charset': 'utf8'}
1122 )
1122 )
1123 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1123 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1126 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1126 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1127
1127
1128 user = relationship('User')
1128 user = relationship('User')
1129 repository = relationship('Repository')
1129 repository = relationship('Repository')
1130 permission = relationship('Permission')
1130 permission = relationship('Permission')
1131
1131
1132 @classmethod
1132 @classmethod
1133 def create(cls, user, repository, permission):
1133 def create(cls, user, repository, permission):
1134 n = cls()
1134 n = cls()
1135 n.user = user
1135 n.user = user
1136 n.repository = repository
1136 n.repository = repository
1137 n.permission = permission
1137 n.permission = permission
1138 Session().add(n)
1138 Session().add(n)
1139 return n
1139 return n
1140
1140
1141 def __unicode__(self):
1141 def __unicode__(self):
1142 return u'<user:%s => %s >' % (self.user, self.repository)
1142 return u'<user:%s => %s >' % (self.user, self.repository)
1143
1143
1144
1144
1145 class UserToPerm(Base, BaseModel):
1145 class UserToPerm(Base, BaseModel):
1146 __tablename__ = 'user_to_perm'
1146 __tablename__ = 'user_to_perm'
1147 __table_args__ = (
1147 __table_args__ = (
1148 UniqueConstraint('user_id', 'permission_id'),
1148 UniqueConstraint('user_id', 'permission_id'),
1149 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1149 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1150 'mysql_charset': 'utf8'}
1150 'mysql_charset': 'utf8'}
1151 )
1151 )
1152 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1152 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1153 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1153 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1154 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1154 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1155
1155
1156 user = relationship('User')
1156 user = relationship('User')
1157 permission = relationship('Permission', lazy='joined')
1157 permission = relationship('Permission', lazy='joined')
1158
1158
1159
1159
1160 class UsersGroupRepoToPerm(Base, BaseModel):
1160 class UsersGroupRepoToPerm(Base, BaseModel):
1161 __tablename__ = 'users_group_repo_to_perm'
1161 __tablename__ = 'users_group_repo_to_perm'
1162 __table_args__ = (
1162 __table_args__ = (
1163 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1163 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1165 'mysql_charset': 'utf8'}
1165 'mysql_charset': 'utf8'}
1166 )
1166 )
1167 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1167 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1168 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1168 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1169 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1169 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1170 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1170 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1171
1171
1172 users_group = relationship('UsersGroup')
1172 users_group = relationship('UsersGroup')
1173 permission = relationship('Permission')
1173 permission = relationship('Permission')
1174 repository = relationship('Repository')
1174 repository = relationship('Repository')
1175
1175
1176 @classmethod
1176 @classmethod
1177 def create(cls, users_group, repository, permission):
1177 def create(cls, users_group, repository, permission):
1178 n = cls()
1178 n = cls()
1179 n.users_group = users_group
1179 n.users_group = users_group
1180 n.repository = repository
1180 n.repository = repository
1181 n.permission = permission
1181 n.permission = permission
1182 Session().add(n)
1182 Session().add(n)
1183 return n
1183 return n
1184
1184
1185 def __unicode__(self):
1185 def __unicode__(self):
1186 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1186 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1187
1187
1188
1188
1189 class UsersGroupToPerm(Base, BaseModel):
1189 class UsersGroupToPerm(Base, BaseModel):
1190 __tablename__ = 'users_group_to_perm'
1190 __tablename__ = 'users_group_to_perm'
1191 __table_args__ = (
1191 __table_args__ = (
1192 UniqueConstraint('users_group_id', 'permission_id',),
1192 UniqueConstraint('users_group_id', 'permission_id',),
1193 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1193 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1194 'mysql_charset': 'utf8'}
1194 'mysql_charset': 'utf8'}
1195 )
1195 )
1196 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1196 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1197 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1197 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1198 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1198 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1199
1199
1200 users_group = relationship('UsersGroup')
1200 users_group = relationship('UsersGroup')
1201 permission = relationship('Permission')
1201 permission = relationship('Permission')
1202
1202
1203
1203
1204 class UserRepoGroupToPerm(Base, BaseModel):
1204 class UserRepoGroupToPerm(Base, BaseModel):
1205 __tablename__ = 'user_repo_group_to_perm'
1205 __tablename__ = 'user_repo_group_to_perm'
1206 __table_args__ = (
1206 __table_args__ = (
1207 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1207 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1208 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1208 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1209 'mysql_charset': 'utf8'}
1209 'mysql_charset': 'utf8'}
1210 )
1210 )
1211
1211
1212 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1212 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1213 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1214 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1214 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1215 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1215 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1216
1216
1217 user = relationship('User')
1217 user = relationship('User')
1218 group = relationship('RepoGroup')
1218 group = relationship('RepoGroup')
1219 permission = relationship('Permission')
1219 permission = relationship('Permission')
1220
1220
1221
1221
1222 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1222 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1223 __tablename__ = 'users_group_repo_group_to_perm'
1223 __tablename__ = 'users_group_repo_group_to_perm'
1224 __table_args__ = (
1224 __table_args__ = (
1225 UniqueConstraint('users_group_id', 'group_id'),
1225 UniqueConstraint('users_group_id', 'group_id'),
1226 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1226 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1227 'mysql_charset': 'utf8'}
1227 'mysql_charset': 'utf8'}
1228 )
1228 )
1229
1229
1230 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1230 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1231 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1231 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1232 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1232 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1233 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1233 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1234
1234
1235 users_group = relationship('UsersGroup')
1235 users_group = relationship('UsersGroup')
1236 permission = relationship('Permission')
1236 permission = relationship('Permission')
1237 group = relationship('RepoGroup')
1237 group = relationship('RepoGroup')
1238
1238
1239
1239
1240 class Statistics(Base, BaseModel):
1240 class Statistics(Base, BaseModel):
1241 __tablename__ = 'statistics'
1241 __tablename__ = 'statistics'
1242 __table_args__ = (
1242 __table_args__ = (
1243 UniqueConstraint('repository_id'),
1243 UniqueConstraint('repository_id'),
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1244 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 'mysql_charset': 'utf8'}
1245 'mysql_charset': 'utf8'}
1246 )
1246 )
1247 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1247 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1248 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1248 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1249 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1249 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1250 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1250 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1251 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1251 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1252 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1252 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1253
1253
1254 repository = relationship('Repository', single_parent=True)
1254 repository = relationship('Repository', single_parent=True)
1255
1255
1256
1256
1257 class UserFollowing(Base, BaseModel):
1257 class UserFollowing(Base, BaseModel):
1258 __tablename__ = 'user_followings'
1258 __tablename__ = 'user_followings'
1259 __table_args__ = (
1259 __table_args__ = (
1260 UniqueConstraint('user_id', 'follows_repository_id'),
1260 UniqueConstraint('user_id', 'follows_repository_id'),
1261 UniqueConstraint('user_id', 'follows_user_id'),
1261 UniqueConstraint('user_id', 'follows_user_id'),
1262 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1262 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1263 'mysql_charset': 'utf8'}
1263 'mysql_charset': 'utf8'}
1264 )
1264 )
1265
1265
1266 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1266 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1267 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1267 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1268 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1268 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1269 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1269 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1270 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1270 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1271
1271
1272 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1272 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1273
1273
1274 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1274 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1275 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1275 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1276
1276
1277 @classmethod
1277 @classmethod
1278 def get_repo_followers(cls, repo_id):
1278 def get_repo_followers(cls, repo_id):
1279 return cls.query().filter(cls.follows_repo_id == repo_id)
1279 return cls.query().filter(cls.follows_repo_id == repo_id)
1280
1280
1281
1281
1282 class CacheInvalidation(Base, BaseModel):
1282 class CacheInvalidation(Base, BaseModel):
1283 __tablename__ = 'cache_invalidation'
1283 __tablename__ = 'cache_invalidation'
1284 __table_args__ = (
1284 __table_args__ = (
1285 UniqueConstraint('cache_key'),
1285 UniqueConstraint('cache_key'),
1286 Index('key_idx', 'cache_key'),
1286 Index('key_idx', 'cache_key'),
1287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1288 'mysql_charset': 'utf8'},
1288 'mysql_charset': 'utf8'},
1289 )
1289 )
1290 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1290 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1291 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1291 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1292 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1292 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1293 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1293 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1294
1294
1295 def __init__(self, cache_key, cache_args=''):
1295 def __init__(self, cache_key, cache_args=''):
1296 self.cache_key = cache_key
1296 self.cache_key = cache_key
1297 self.cache_args = cache_args
1297 self.cache_args = cache_args
1298 self.cache_active = False
1298 self.cache_active = False
1299
1299
1300 def __unicode__(self):
1300 def __unicode__(self):
1301 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1301 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1302 self.cache_id, self.cache_key)
1302 self.cache_id, self.cache_key)
1303
1303
1304 @classmethod
1304 @classmethod
1305 def clear_cache(cls):
1305 def clear_cache(cls):
1306 cls.query().delete()
1306 cls.query().delete()
1307
1307
1308 @classmethod
1308 @classmethod
1309 def _get_key(cls, key):
1309 def _get_key(cls, key):
1310 """
1310 """
1311 Wrapper for generating a key, together with a prefix
1311 Wrapper for generating a key, together with a prefix
1312
1312
1313 :param key:
1313 :param key:
1314 """
1314 """
1315 import rhodecode
1315 import rhodecode
1316 prefix = ''
1316 prefix = ''
1317 iid = rhodecode.CONFIG.get('instance_id')
1317 iid = rhodecode.CONFIG.get('instance_id')
1318 if iid:
1318 if iid:
1319 prefix = iid
1319 prefix = iid
1320 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1320 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1321
1321
1322 @classmethod
1322 @classmethod
1323 def get_by_key(cls, key):
1323 def get_by_key(cls, key):
1324 return cls.query().filter(cls.cache_key == key).scalar()
1324 return cls.query().filter(cls.cache_key == key).scalar()
1325
1325
1326 @classmethod
1326 @classmethod
1327 def _get_or_create_key(cls, key, prefix, org_key):
1327 def _get_or_create_key(cls, key, prefix, org_key):
1328 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1328 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1329 if not inv_obj:
1329 if not inv_obj:
1330 try:
1330 try:
1331 inv_obj = CacheInvalidation(key, org_key)
1331 inv_obj = CacheInvalidation(key, org_key)
1332 Session().add(inv_obj)
1332 Session().add(inv_obj)
1333 Session().commit()
1333 Session().commit()
1334 except Exception:
1334 except Exception:
1335 log.error(traceback.format_exc())
1335 log.error(traceback.format_exc())
1336 Session().rollback()
1336 Session().rollback()
1337 return inv_obj
1337 return inv_obj
1338
1338
1339 @classmethod
1339 @classmethod
1340 def invalidate(cls, key):
1340 def invalidate(cls, key):
1341 """
1341 """
1342 Returns Invalidation object if this given key should be invalidated
1342 Returns Invalidation object if this given key should be invalidated
1343 None otherwise. `cache_active = False` means that this cache
1343 None otherwise. `cache_active = False` means that this cache
1344 state is not valid and needs to be invalidated
1344 state is not valid and needs to be invalidated
1345
1345
1346 :param key:
1346 :param key:
1347 """
1347 """
1348
1348
1349 key, _prefix, _org_key = cls._get_key(key)
1349 key, _prefix, _org_key = cls._get_key(key)
1350 inv = cls._get_or_create_key(key, _prefix, _org_key)
1350 inv = cls._get_or_create_key(key, _prefix, _org_key)
1351
1351
1352 if inv and inv.cache_active is False:
1352 if inv and inv.cache_active is False:
1353 return inv
1353 return inv
1354
1354
1355 @classmethod
1355 @classmethod
1356 def set_invalidate(cls, key):
1356 def set_invalidate(cls, key):
1357 """
1357 """
1358 Mark this Cache key for invalidation
1358 Mark this Cache key for invalidation
1359
1359
1360 :param key:
1360 :param key:
1361 """
1361 """
1362
1362
1363 key, _prefix, _org_key = cls._get_key(key)
1363 key, _prefix, _org_key = cls._get_key(key)
1364 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1364 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1365 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1365 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1366 _org_key))
1366 _org_key))
1367 try:
1367 try:
1368 for inv_obj in inv_objs:
1368 for inv_obj in inv_objs:
1369 if inv_obj:
1369 if inv_obj:
1370 inv_obj.cache_active = False
1370 inv_obj.cache_active = False
1371
1371
1372 Session().add(inv_obj)
1372 Session().add(inv_obj)
1373 Session().commit()
1373 Session().commit()
1374 except Exception:
1374 except Exception:
1375 log.error(traceback.format_exc())
1375 log.error(traceback.format_exc())
1376 Session().rollback()
1376 Session().rollback()
1377
1377
1378 @classmethod
1378 @classmethod
1379 def set_valid(cls, key):
1379 def set_valid(cls, key):
1380 """
1380 """
1381 Mark this cache key as active and currently cached
1381 Mark this cache key as active and currently cached
1382
1382
1383 :param key:
1383 :param key:
1384 """
1384 """
1385 inv_obj = cls.get_by_key(key)
1385 inv_obj = cls.get_by_key(key)
1386 inv_obj.cache_active = True
1386 inv_obj.cache_active = True
1387 Session().add(inv_obj)
1387 Session().add(inv_obj)
1388 Session().commit()
1388 Session().commit()
1389
1389
1390 @classmethod
1390 @classmethod
1391 def get_cache_map(cls):
1391 def get_cache_map(cls):
1392
1392
1393 class cachemapdict(dict):
1393 class cachemapdict(dict):
1394
1394
1395 def __init__(self, *args, **kwargs):
1395 def __init__(self, *args, **kwargs):
1396 fixkey = kwargs.get('fixkey')
1396 fixkey = kwargs.get('fixkey')
1397 if fixkey:
1397 if fixkey:
1398 del kwargs['fixkey']
1398 del kwargs['fixkey']
1399 self.fixkey = fixkey
1399 self.fixkey = fixkey
1400 super(cachemapdict, self).__init__(*args, **kwargs)
1400 super(cachemapdict, self).__init__(*args, **kwargs)
1401
1401
1402 def __getattr__(self, name):
1402 def __getattr__(self, name):
1403 key = name
1403 key = name
1404 if self.fixkey:
1404 if self.fixkey:
1405 key, _prefix, _org_key = cls._get_key(key)
1405 key, _prefix, _org_key = cls._get_key(key)
1406 if key in self.__dict__:
1406 if key in self.__dict__:
1407 return self.__dict__[key]
1407 return self.__dict__[key]
1408 else:
1408 else:
1409 return self[key]
1409 return self[key]
1410
1410
1411 def __getitem__(self, key):
1411 def __getitem__(self, key):
1412 if self.fixkey:
1412 if self.fixkey:
1413 key, _prefix, _org_key = cls._get_key(key)
1413 key, _prefix, _org_key = cls._get_key(key)
1414 try:
1414 try:
1415 return super(cachemapdict, self).__getitem__(key)
1415 return super(cachemapdict, self).__getitem__(key)
1416 except KeyError:
1416 except KeyError:
1417 return
1417 return
1418
1418
1419 cache_map = cachemapdict(fixkey=True)
1419 cache_map = cachemapdict(fixkey=True)
1420 for obj in cls.query().all():
1420 for obj in cls.query().all():
1421 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1421 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1422 return cache_map
1422 return cache_map
1423
1423
1424
1424
1425 class ChangesetComment(Base, BaseModel):
1425 class ChangesetComment(Base, BaseModel):
1426 __tablename__ = 'changeset_comments'
1426 __tablename__ = 'changeset_comments'
1427 __table_args__ = (
1427 __table_args__ = (
1428 Index('cc_revision_idx', 'revision'),
1428 Index('cc_revision_idx', 'revision'),
1429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1429 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1430 'mysql_charset': 'utf8'},
1430 'mysql_charset': 'utf8'},
1431 )
1431 )
1432 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1432 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1433 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1433 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1434 revision = Column('revision', String(40), nullable=True)
1434 revision = Column('revision', String(40), nullable=True)
1435 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1435 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1436 line_no = Column('line_no', Unicode(10), nullable=True)
1436 line_no = Column('line_no', Unicode(10), nullable=True)
1437 f_path = Column('f_path', Unicode(1000), nullable=True)
1437 f_path = Column('f_path', Unicode(1000), nullable=True)
1438 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1438 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1439 text = Column('text', Unicode(25000), nullable=False)
1439 text = Column('text', Unicode(25000), nullable=False)
1440 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1440 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1441 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1441 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1442
1442
1443 author = relationship('User', lazy='joined')
1443 author = relationship('User', lazy='joined')
1444 repo = relationship('Repository')
1444 repo = relationship('Repository')
1445 status_change = relationship('ChangesetStatus', uselist=False)
1445 status_change = relationship('ChangesetStatus', uselist=False)
1446 pull_request = relationship('PullRequest', lazy='joined')
1446 pull_request = relationship('PullRequest', lazy='joined')
1447
1447
1448 @classmethod
1448 @classmethod
1449 def get_users(cls, revision=None, pull_request_id=None):
1449 def get_users(cls, revision=None, pull_request_id=None):
1450 """
1450 """
1451 Returns user associated with this ChangesetComment. ie those
1451 Returns user associated with this ChangesetComment. ie those
1452 who actually commented
1452 who actually commented
1453
1453
1454 :param cls:
1454 :param cls:
1455 :param revision:
1455 :param revision:
1456 """
1456 """
1457 q = Session().query(User)\
1457 q = Session().query(User)\
1458 .join(ChangesetComment.author)
1458 .join(ChangesetComment.author)
1459 if revision:
1459 if revision:
1460 q = q.filter(cls.revision == revision)
1460 q = q.filter(cls.revision == revision)
1461 elif pull_request_id:
1461 elif pull_request_id:
1462 q = q.filter(cls.pull_request_id == pull_request_id)
1462 q = q.filter(cls.pull_request_id == pull_request_id)
1463 return q.all()
1463 return q.all()
1464
1464
1465
1465
1466 class ChangesetStatus(Base, BaseModel):
1466 class ChangesetStatus(Base, BaseModel):
1467 __tablename__ = 'changeset_statuses'
1467 __tablename__ = 'changeset_statuses'
1468 __table_args__ = (
1468 __table_args__ = (
1469 Index('cs_revision_idx', 'revision'),
1469 Index('cs_revision_idx', 'revision'),
1470 Index('cs_version_idx', 'version'),
1470 Index('cs_version_idx', 'version'),
1471 UniqueConstraint('repo_id', 'revision', 'version'),
1471 UniqueConstraint('repo_id', 'revision', 'version'),
1472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1472 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1473 'mysql_charset': 'utf8'}
1473 'mysql_charset': 'utf8'}
1474 )
1474 )
1475 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1475 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1476 STATUS_APPROVED = 'approved'
1476 STATUS_APPROVED = 'approved'
1477 STATUS_REJECTED = 'rejected'
1477 STATUS_REJECTED = 'rejected'
1478 STATUS_UNDER_REVIEW = 'under_review'
1478 STATUS_UNDER_REVIEW = 'under_review'
1479
1479
1480 STATUSES = [
1480 STATUSES = [
1481 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1481 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1482 (STATUS_APPROVED, _("Approved")),
1482 (STATUS_APPROVED, _("Approved")),
1483 (STATUS_REJECTED, _("Rejected")),
1483 (STATUS_REJECTED, _("Rejected")),
1484 (STATUS_UNDER_REVIEW, _("Under Review")),
1484 (STATUS_UNDER_REVIEW, _("Under Review")),
1485 ]
1485 ]
1486
1486
1487 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1487 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1488 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1488 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1489 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1489 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1490 revision = Column('revision', String(40), nullable=False)
1490 revision = Column('revision', String(40), nullable=False)
1491 status = Column('status', String(128), nullable=False, default=DEFAULT)
1491 status = Column('status', String(128), nullable=False, default=DEFAULT)
1492 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1492 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1493 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1493 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1494 version = Column('version', Integer(), nullable=False, default=0)
1494 version = Column('version', Integer(), nullable=False, default=0)
1495 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1495 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1496
1496
1497 author = relationship('User', lazy='joined')
1497 author = relationship('User', lazy='joined')
1498 repo = relationship('Repository')
1498 repo = relationship('Repository')
1499 comment = relationship('ChangesetComment', lazy='joined')
1499 comment = relationship('ChangesetComment', lazy='joined')
1500 pull_request = relationship('PullRequest', lazy='joined')
1500 pull_request = relationship('PullRequest', lazy='joined')
1501
1501
1502 def __unicode__(self):
1502 def __unicode__(self):
1503 return u"<%s('%s:%s')>" % (
1503 return u"<%s('%s:%s')>" % (
1504 self.__class__.__name__,
1504 self.__class__.__name__,
1505 self.status, self.author
1505 self.status, self.author
1506 )
1506 )
1507
1507
1508 @classmethod
1508 @classmethod
1509 def get_status_lbl(cls, value):
1509 def get_status_lbl(cls, value):
1510 return dict(cls.STATUSES).get(value)
1510 return dict(cls.STATUSES).get(value)
1511
1511
1512 @property
1512 @property
1513 def status_lbl(self):
1513 def status_lbl(self):
1514 return ChangesetStatus.get_status_lbl(self.status)
1514 return ChangesetStatus.get_status_lbl(self.status)
1515
1515
1516
1516
1517 class PullRequest(Base, BaseModel):
1517 class PullRequest(Base, BaseModel):
1518 __tablename__ = 'pull_requests'
1518 __tablename__ = 'pull_requests'
1519 __table_args__ = (
1519 __table_args__ = (
1520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1521 'mysql_charset': 'utf8'},
1521 'mysql_charset': 'utf8'},
1522 )
1522 )
1523
1523
1524 STATUS_NEW = u'new'
1524 STATUS_NEW = u'new'
1525 STATUS_OPEN = u'open'
1525 STATUS_OPEN = u'open'
1526 STATUS_CLOSED = u'closed'
1526 STATUS_CLOSED = u'closed'
1527
1527
1528 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1528 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1529 title = Column('title', Unicode(256), nullable=True)
1529 title = Column('title', Unicode(256), nullable=True)
1530 description = Column('description', UnicodeText(10240), nullable=True)
1530 description = Column('description', UnicodeText(10240), nullable=True)
1531 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1531 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1532 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1532 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1533 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1533 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1535 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1535 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1536 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1536 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1537 org_ref = Column('org_ref', Unicode(256), nullable=False)
1537 org_ref = Column('org_ref', Unicode(256), nullable=False)
1538 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1538 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1539 other_ref = Column('other_ref', Unicode(256), nullable=False)
1539 other_ref = Column('other_ref', Unicode(256), nullable=False)
1540
1540
1541 @hybrid_property
1541 @hybrid_property
1542 def revisions(self):
1542 def revisions(self):
1543 return self._revisions.split(':')
1543 return self._revisions.split(':')
1544
1544
1545 @revisions.setter
1545 @revisions.setter
1546 def revisions(self, val):
1546 def revisions(self, val):
1547 self._revisions = ':'.join(val)
1547 self._revisions = ':'.join(val)
1548
1548
1549 author = relationship('User', lazy='joined')
1549 author = relationship('User', lazy='joined')
1550 reviewers = relationship('PullRequestReviewers',
1550 reviewers = relationship('PullRequestReviewers',
1551 cascade="all, delete, delete-orphan")
1551 cascade="all, delete, delete-orphan")
1552 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1552 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1553 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1553 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1554 statuses = relationship('ChangesetStatus')
1554 statuses = relationship('ChangesetStatus')
1555 comments = relationship('ChangesetComment',
1555 comments = relationship('ChangesetComment',
1556 cascade="all, delete, delete-orphan")
1556 cascade="all, delete, delete-orphan")
1557
1557
1558 def is_closed(self):
1558 def is_closed(self):
1559 return self.status == self.STATUS_CLOSED
1559 return self.status == self.STATUS_CLOSED
1560
1560
1561 def __json__(self):
1561 def __json__(self):
1562 return dict(
1562 return dict(
1563 revisions=self.revisions
1563 revisions=self.revisions
1564 )
1564 )
1565
1565
1566
1566
1567 class PullRequestReviewers(Base, BaseModel):
1567 class PullRequestReviewers(Base, BaseModel):
1568 __tablename__ = 'pull_request_reviewers'
1568 __tablename__ = 'pull_request_reviewers'
1569 __table_args__ = (
1569 __table_args__ = (
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 'mysql_charset': 'utf8'},
1571 'mysql_charset': 'utf8'},
1572 )
1572 )
1573
1573
1574 def __init__(self, user=None, pull_request=None):
1574 def __init__(self, user=None, pull_request=None):
1575 self.user = user
1575 self.user = user
1576 self.pull_request = pull_request
1576 self.pull_request = pull_request
1577
1577
1578 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1578 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1579 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1579 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1580 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1580 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1581
1581
1582 user = relationship('User')
1582 user = relationship('User')
1583 pull_request = relationship('PullRequest')
1583 pull_request = relationship('PullRequest')
1584
1584
1585
1585
1586 class Notification(Base, BaseModel):
1586 class Notification(Base, BaseModel):
1587 __tablename__ = 'notifications'
1587 __tablename__ = 'notifications'
1588 __table_args__ = (
1588 __table_args__ = (
1589 Index('notification_type_idx', 'type'),
1589 Index('notification_type_idx', 'type'),
1590 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1590 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1591 'mysql_charset': 'utf8'},
1591 'mysql_charset': 'utf8'},
1592 )
1592 )
1593
1593
1594 TYPE_CHANGESET_COMMENT = u'cs_comment'
1594 TYPE_CHANGESET_COMMENT = u'cs_comment'
1595 TYPE_MESSAGE = u'message'
1595 TYPE_MESSAGE = u'message'
1596 TYPE_MENTION = u'mention'
1596 TYPE_MENTION = u'mention'
1597 TYPE_REGISTRATION = u'registration'
1597 TYPE_REGISTRATION = u'registration'
1598 TYPE_PULL_REQUEST = u'pull_request'
1598 TYPE_PULL_REQUEST = u'pull_request'
1599 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1599 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1600
1600
1601 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1601 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1602 subject = Column('subject', Unicode(512), nullable=True)
1602 subject = Column('subject', Unicode(512), nullable=True)
1603 body = Column('body', UnicodeText(50000), nullable=True)
1603 body = Column('body', UnicodeText(50000), nullable=True)
1604 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1604 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1605 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1605 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1606 type_ = Column('type', Unicode(256))
1606 type_ = Column('type', Unicode(256))
1607
1607
1608 created_by_user = relationship('User')
1608 created_by_user = relationship('User')
1609 notifications_to_users = relationship('UserNotification', lazy='joined',
1609 notifications_to_users = relationship('UserNotification', lazy='joined',
1610 cascade="all, delete, delete-orphan")
1610 cascade="all, delete, delete-orphan")
1611
1611
1612 @property
1612 @property
1613 def recipients(self):
1613 def recipients(self):
1614 return [x.user for x in UserNotification.query()\
1614 return [x.user for x in UserNotification.query()\
1615 .filter(UserNotification.notification == self)\
1615 .filter(UserNotification.notification == self)\
1616 .order_by(UserNotification.user_id.asc()).all()]
1616 .order_by(UserNotification.user_id.asc()).all()]
1617
1617
1618 @classmethod
1618 @classmethod
1619 def create(cls, created_by, subject, body, recipients, type_=None):
1619 def create(cls, created_by, subject, body, recipients, type_=None):
1620 if type_ is None:
1620 if type_ is None:
1621 type_ = Notification.TYPE_MESSAGE
1621 type_ = Notification.TYPE_MESSAGE
1622
1622
1623 notification = cls()
1623 notification = cls()
1624 notification.created_by_user = created_by
1624 notification.created_by_user = created_by
1625 notification.subject = subject
1625 notification.subject = subject
1626 notification.body = body
1626 notification.body = body
1627 notification.type_ = type_
1627 notification.type_ = type_
1628 notification.created_on = datetime.datetime.now()
1628 notification.created_on = datetime.datetime.now()
1629
1629
1630 for u in recipients:
1630 for u in recipients:
1631 assoc = UserNotification()
1631 assoc = UserNotification()
1632 assoc.notification = notification
1632 assoc.notification = notification
1633 u.notifications.append(assoc)
1633 u.notifications.append(assoc)
1634 Session().add(notification)
1634 Session().add(notification)
1635 return notification
1635 return notification
1636
1636
1637 @property
1637 @property
1638 def description(self):
1638 def description(self):
1639 from rhodecode.model.notification import NotificationModel
1639 from rhodecode.model.notification import NotificationModel
1640 return NotificationModel().make_description(self)
1640 return NotificationModel().make_description(self)
1641
1641
1642
1642
1643 class UserNotification(Base, BaseModel):
1643 class UserNotification(Base, BaseModel):
1644 __tablename__ = 'user_to_notification'
1644 __tablename__ = 'user_to_notification'
1645 __table_args__ = (
1645 __table_args__ = (
1646 UniqueConstraint('user_id', 'notification_id'),
1646 UniqueConstraint('user_id', 'notification_id'),
1647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1647 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1648 'mysql_charset': 'utf8'}
1648 'mysql_charset': 'utf8'}
1649 )
1649 )
1650 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1650 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1651 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1651 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1652 read = Column('read', Boolean, default=False)
1652 read = Column('read', Boolean, default=False)
1653 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1653 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1654
1654
1655 user = relationship('User', lazy="joined")
1655 user = relationship('User', lazy="joined")
1656 notification = relationship('Notification', lazy="joined",
1656 notification = relationship('Notification', lazy="joined",
1657 order_by=lambda: Notification.created_on.desc(),)
1657 order_by=lambda: Notification.created_on.desc(),)
1658
1658
1659 def mark_as_read(self):
1659 def mark_as_read(self):
1660 self.read = True
1660 self.read = True
1661 Session().add(self)
1661 Session().add(self)
1662
1662
1663
1663
1664 class DbMigrateVersion(Base, BaseModel):
1664 class DbMigrateVersion(Base, BaseModel):
1665 __tablename__ = 'db_migrate_version'
1665 __tablename__ = 'db_migrate_version'
1666 __table_args__ = (
1666 __table_args__ = (
1667 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1667 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1668 'mysql_charset': 'utf8'},
1668 'mysql_charset': 'utf8'},
1669 )
1669 )
1670 repository_id = Column('repository_id', String(250), primary_key=True)
1670 repository_id = Column('repository_id', String(250), primary_key=True)
1671 repository_path = Column('repository_path', Text)
1671 repository_path = Column('repository_path', Text)
1672 version = Column('version', Integer)
1672 version = Column('version', Integer)
@@ -1,246 +1,246 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Settings administration')} - ${c.rhodecode_name}
5 ${_('Settings administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22 <!-- end box / title -->
22 <!-- end box / title -->
23
23
24 <h3>${_('Remap and rescan repositories')}</h3>
24 <h3>${_('Remap and rescan repositories')}</h3>
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28
28
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label label-checkbox">
31 <div class="label label-checkbox">
32 <label for="destroy">${_('rescan option')}:</label>
32 <label for="destroy">${_('rescan option')}:</label>
33 </div>
33 </div>
34 <div class="checkboxes">
34 <div class="checkboxes">
35 <div class="checkbox">
35 <div class="checkbox">
36 ${h.checkbox('destroy',True)}
36 ${h.checkbox('destroy',True)}
37 <label for="destroy">
37 <label for="destroy">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 ${_('destroy old data')}</span> </label>
39 ${_('destroy old data')}</span> </label>
40 </div>
40 </div>
41 </div>
41 </div>
42 </div>
42 </div>
43
43
44 <div class="buttons">
44 <div class="buttons">
45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
45 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
46 </div>
46 </div>
47 </div>
47 </div>
48 </div>
48 </div>
49 ${h.end_form()}
49 ${h.end_form()}
50
50
51 <h3>${_('Whoosh indexing')}</h3>
51 <h3>${_('Whoosh indexing')}</h3>
52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
52 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
53 <div class="form">
53 <div class="form">
54 <!-- fields -->
54 <!-- fields -->
55
55
56 <div class="fields">
56 <div class="fields">
57 <div class="field">
57 <div class="field">
58 <div class="label label-checkbox">
58 <div class="label label-checkbox">
59 <label>${_('index build option')}:</label>
59 <label>${_('index build option')}:</label>
60 </div>
60 </div>
61 <div class="checkboxes">
61 <div class="checkboxes">
62 <div class="checkbox">
62 <div class="checkbox">
63 ${h.checkbox('full_index',True)}
63 ${h.checkbox('full_index',True)}
64 <label for="full_index">${_('build from scratch')}</label>
64 <label for="full_index">${_('build from scratch')}</label>
65 </div>
65 </div>
66 </div>
66 </div>
67 </div>
67 </div>
68
68
69 <div class="buttons">
69 <div class="buttons">
70 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
70 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
71 </div>
71 </div>
72 </div>
72 </div>
73 </div>
73 </div>
74 ${h.end_form()}
74 ${h.end_form()}
75
75
76 <h3>${_('Global application settings')}</h3>
76 <h3>${_('Global application settings')}</h3>
77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
77 ${h.form(url('admin_setting', setting_id='global'),method='put')}
78 <div class="form">
78 <div class="form">
79 <!-- fields -->
79 <!-- fields -->
80
80
81 <div class="fields">
81 <div class="fields">
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label for="rhodecode_title">${_('Application name')}:</label>
85 <label for="rhodecode_title">${_('Application name')}:</label>
86 </div>
86 </div>
87 <div class="input">
87 <div class="input">
88 ${h.text('rhodecode_title',size=30)}
88 ${h.text('rhodecode_title',size=30)}
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92 <div class="field">
92 <div class="field">
93 <div class="label">
93 <div class="label">
94 <label for="rhodecode_realm">${_('Realm text')}:</label>
94 <label for="rhodecode_realm">${_('Realm text')}:</label>
95 </div>
95 </div>
96 <div class="input">
96 <div class="input">
97 ${h.text('rhodecode_realm',size=30)}
97 ${h.text('rhodecode_realm',size=30)}
98 </div>
98 </div>
99 </div>
99 </div>
100
100
101 <div class="field">
101 <div class="field">
102 <div class="label">
102 <div class="label">
103 <label for="rhodecode_ga_code">${_('GA code')}:</label>
103 <label for="rhodecode_ga_code">${_('GA code')}:</label>
104 </div>
104 </div>
105 <div class="input">
105 <div class="input">
106 ${h.text('rhodecode_ga_code',size=30)}
106 ${h.text('rhodecode_ga_code',size=30)}
107 </div>
107 </div>
108 </div>
108 </div>
109
109
110 <div class="buttons">
110 <div class="buttons">
111 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
111 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
112 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
112 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
113 </div>
113 </div>
114 </div>
114 </div>
115 </div>
115 </div>
116 ${h.end_form()}
116 ${h.end_form()}
117
117
118 <h3>${_('VCS settings')}</h3>
118 <h3>${_('VCS settings')}</h3>
119 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
119 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
120 <div class="form">
120 <div class="form">
121 <!-- fields -->
121 <!-- fields -->
122
122
123 <div class="fields">
123 <div class="fields">
124
124
125 <div class="field">
125 <div class="field">
126 <div class="label label-checkbox">
126 <div class="label label-checkbox">
127 <label>${_('Web')}:</label>
127 <label>${_('Web')}:</label>
128 </div>
128 </div>
129 <div class="checkboxes">
129 <div class="checkboxes">
130 <div class="checkbox">
130 <div class="checkbox">
131 ${h.checkbox('web_push_ssl','true')}
131 ${h.checkbox('web_push_ssl','true')}
132 <label for="web_push_ssl">${_('require ssl for pushing')}</label>
132 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
133 </div>
133 </div>
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 <div class="field">
137 <div class="field">
138 <div class="label label-checkbox">
138 <div class="label label-checkbox">
139 <label>${_('Hooks')}:</label>
139 <label>${_('Hooks')}:</label>
140 </div>
140 </div>
141 <div class="checkboxes">
141 <div class="checkboxes">
142 <div class="checkbox">
142 <div class="checkbox">
143 ${h.checkbox('hooks_changegroup_update','True')}
143 ${h.checkbox('hooks_changegroup_update','True')}
144 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
144 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
145 </div>
145 </div>
146 <div class="checkbox">
146 <div class="checkbox">
147 ${h.checkbox('hooks_changegroup_repo_size','True')}
147 ${h.checkbox('hooks_changegroup_repo_size','True')}
148 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
148 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
149 </div>
149 </div>
150 <div class="checkbox">
150 <div class="checkbox">
151 ${h.checkbox('hooks_changegroup_push_logger','True')}
151 ${h.checkbox('hooks_changegroup_push_logger','True')}
152 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
152 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
153 </div>
153 </div>
154 <div class="checkbox">
154 <div class="checkbox">
155 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
155 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
156 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
156 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
157 </div>
157 </div>
158 </div>
158 </div>
159 <div class="input" style="margin-top:10px">
159 <div class="input" style="margin-top:10px">
160 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
160 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
161 </div>
161 </div>
162 </div>
162 </div>
163 <div class="field">
163 <div class="field">
164 <div class="label">
164 <div class="label">
165 <label for="paths_root_path">${_('Repositories location')}:</label>
165 <label for="paths_root_path">${_('Repositories location')}:</label>
166 </div>
166 </div>
167 <div class="input">
167 <div class="input">
168 ${h.text('paths_root_path',size=30,readonly="readonly")}
168 ${h.text('paths_root_path',size=30,readonly="readonly")}
169 <span id="path_unlock" class="tooltip"
169 <span id="path_unlock" class="tooltip"
170 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
170 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
171 ${_('unlock')}</span>
171 ${_('unlock')}</span>
172 </div>
172 </div>
173 </div>
173 </div>
174
174
175 <div class="buttons">
175 <div class="buttons">
176 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
176 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
177 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
177 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
178 </div>
178 </div>
179 </div>
179 </div>
180 </div>
180 </div>
181 ${h.end_form()}
181 ${h.end_form()}
182
182
183 <script type="text/javascript">
183 <script type="text/javascript">
184 YAHOO.util.Event.onDOMReady(function(){
184 YAHOO.util.Event.onDOMReady(function(){
185 YAHOO.util.Event.addListener('path_unlock','click',function(){
185 YAHOO.util.Event.addListener('path_unlock','click',function(){
186 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
186 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
187 });
187 });
188 });
188 });
189 </script>
189 </script>
190
190
191 <h3>${_('Test Email')}</h3>
191 <h3>${_('Test Email')}</h3>
192 ${h.form(url('admin_setting', setting_id='email'),method='put')}
192 ${h.form(url('admin_setting', setting_id='email'),method='put')}
193 <div class="form">
193 <div class="form">
194 <!-- fields -->
194 <!-- fields -->
195
195
196 <div class="fields">
196 <div class="fields">
197 <div class="field">
197 <div class="field">
198 <div class="label">
198 <div class="label">
199 <label for="test_email">${_('Email to')}:</label>
199 <label for="test_email">${_('Email to')}:</label>
200 </div>
200 </div>
201 <div class="input">
201 <div class="input">
202 ${h.text('test_email',size=30)}
202 ${h.text('test_email',size=30)}
203 </div>
203 </div>
204 </div>
204 </div>
205
205
206 <div class="buttons">
206 <div class="buttons">
207 ${h.submit('send',_('Send'),class_="ui-btn large")}
207 ${h.submit('send',_('Send'),class_="ui-btn large")}
208 </div>
208 </div>
209 </div>
209 </div>
210 </div>
210 </div>
211 ${h.end_form()}
211 ${h.end_form()}
212
212
213 <h3>${_('System Info and Packages')}</h3>
213 <h3>${_('System Info and Packages')}</h3>
214 <div class="form">
214 <div class="form">
215 <div>
215 <div>
216 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
216 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
217 </div>
217 </div>
218 <div id="expand_modules_table" style="display:none">
218 <div id="expand_modules_table" style="display:none">
219 <h5>Python - ${c.py_version}</h5>
219 <h5>Python - ${c.py_version}</h5>
220 <h5>System - ${c.platform}</h5>
220 <h5>System - ${c.platform}</h5>
221
221
222 <table class="table" style="margin:0px 0px 0px 20px">
222 <table class="table" style="margin:0px 0px 0px 20px">
223 <colgroup>
223 <colgroup>
224 <col style="width:220px">
224 <col style="width:220px">
225 </colgroup>
225 </colgroup>
226 <tbody>
226 <tbody>
227 %for key, value in c.modules:
227 %for key, value in c.modules:
228 <tr>
228 <tr>
229 <th style="text-align: right;padding-right:5px;">${key}</th>
229 <th style="text-align: right;padding-right:5px;">${key}</th>
230 <td>${value}</td>
230 <td>${value}</td>
231 </tr>
231 </tr>
232 %endfor
232 %endfor
233 </tbody>
233 </tbody>
234 </table>
234 </table>
235 </div>
235 </div>
236 </div>
236 </div>
237
237
238 <script type="text/javascript">
238 <script type="text/javascript">
239 YUE.on('expand_modules','click',function(e){
239 YUE.on('expand_modules','click',function(e){
240 YUD.setStyle('expand_modules_table','display','');
240 YUD.setStyle('expand_modules_table','display','');
241 YUD.setStyle('expand_modules','display','none');
241 YUD.setStyle('expand_modules','display','none');
242 })
242 })
243 </script>
243 </script>
244
244
245 </div>
245 </div>
246 </%def>
246 </%def>
General Comments 0
You need to be logged in to leave comments. Login now