##// END OF EJS Templates
no_cache version of scm is now a function
marcink -
r3549:e4a4006f beta
parent child Browse files
Show More
@@ -1,468 +1,468 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.hooks
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Hooks runned by rhodecode
7 7
8 8 :created_on: Aug 6, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import os
26 26 import sys
27 27 import time
28 28 import binascii
29 29 from inspect import isfunction
30 30
31 31 from mercurial.scmutil import revrange
32 32 from mercurial.node import nullrev
33 33
34 34 from rhodecode.lib import helpers as h
35 35 from rhodecode.lib.utils import action_logger
36 36 from rhodecode.lib.vcs.backends.base import EmptyChangeset
37 37 from rhodecode.lib.compat import json
38 38 from rhodecode.lib.exceptions import HTTPLockedRC
39 39 from rhodecode.lib.utils2 import safe_str
40 40 from rhodecode.model.db import Repository, User
41 41
42 42
43 43 def _get_scm_size(alias, root_path):
44 44
45 45 if not alias.startswith('.'):
46 46 alias += '.'
47 47
48 48 size_scm, size_root = 0, 0
49 49 for path, dirs, files in os.walk(safe_str(root_path)):
50 50 if path.find(alias) != -1:
51 51 for f in files:
52 52 try:
53 53 size_scm += os.path.getsize(os.path.join(path, f))
54 54 except OSError:
55 55 pass
56 56 else:
57 57 for f in files:
58 58 try:
59 59 size_root += os.path.getsize(os.path.join(path, f))
60 60 except OSError:
61 61 pass
62 62
63 63 size_scm_f = h.format_byte_size(size_scm)
64 64 size_root_f = h.format_byte_size(size_root)
65 65 size_total_f = h.format_byte_size(size_root + size_scm)
66 66
67 67 return size_scm_f, size_root_f, size_total_f
68 68
69 69
70 70 def repo_size(ui, repo, hooktype=None, **kwargs):
71 71 """
72 72 Presents size of repository after push
73 73
74 74 :param ui:
75 75 :param repo:
76 76 :param hooktype:
77 77 """
78 78
79 79 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
80 80
81 81 last_cs = repo[len(repo) - 1]
82 82
83 83 msg = ('Repository size .hg:%s repo:%s total:%s\n'
84 84 'Last revision is now r%s:%s\n') % (
85 85 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
86 86 )
87 87
88 88 sys.stdout.write(msg)
89 89
90 90
91 91 def pre_push(ui, repo, **kwargs):
92 92 # pre push function, currently used to ban pushing when
93 93 # repository is locked
94 94 try:
95 95 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
96 96 except:
97 97 rc_extras = {}
98 98 extras = dict(repo.ui.configitems('rhodecode_extras'))
99 99
100 100 if 'username' in extras:
101 101 username = extras['username']
102 102 repository = extras['repository']
103 103 scm = extras['scm']
104 104 locked_by = extras['locked_by']
105 105 elif 'username' in rc_extras:
106 106 username = rc_extras['username']
107 107 repository = rc_extras['repository']
108 108 scm = rc_extras['scm']
109 109 locked_by = rc_extras['locked_by']
110 110 else:
111 111 raise Exception('Missing data in repo.ui and os.environ')
112 112
113 113 usr = User.get_by_username(username)
114 114 if locked_by[0] and usr.user_id != int(locked_by[0]):
115 115 locked_by = User.get(locked_by[0]).username
116 116 # this exception is interpreted in git/hg middlewares and based
117 117 # on that proper return code is server to client
118 118 _http_ret = HTTPLockedRC(repository, locked_by)
119 119 if str(_http_ret.code).startswith('2'):
120 120 #2xx Codes don't raise exceptions
121 121 sys.stdout.write(_http_ret.title)
122 122 else:
123 123 raise _http_ret
124 124
125 125
126 126 def pre_pull(ui, repo, **kwargs):
127 127 # pre push function, currently used to ban pushing when
128 128 # repository is locked
129 129 try:
130 130 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
131 131 except:
132 132 rc_extras = {}
133 133 extras = dict(repo.ui.configitems('rhodecode_extras'))
134 134 if 'username' in extras:
135 135 username = extras['username']
136 136 repository = extras['repository']
137 137 scm = extras['scm']
138 138 locked_by = extras['locked_by']
139 139 elif 'username' in rc_extras:
140 140 username = rc_extras['username']
141 141 repository = rc_extras['repository']
142 142 scm = rc_extras['scm']
143 143 locked_by = rc_extras['locked_by']
144 144 else:
145 145 raise Exception('Missing data in repo.ui and os.environ')
146 146
147 147 if locked_by[0]:
148 148 locked_by = User.get(locked_by[0]).username
149 149 # this exception is interpreted in git/hg middlewares and based
150 150 # on that proper return code is server to client
151 151 _http_ret = HTTPLockedRC(repository, locked_by)
152 152 if str(_http_ret.code).startswith('2'):
153 153 #2xx Codes don't raise exceptions
154 154 sys.stdout.write(_http_ret.title)
155 155 else:
156 156 raise _http_ret
157 157
158 158
159 159 def log_pull_action(ui, repo, **kwargs):
160 160 """
161 161 Logs user last pull action
162 162
163 163 :param ui:
164 164 :param repo:
165 165 """
166 166 try:
167 167 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
168 168 except:
169 169 rc_extras = {}
170 170 extras = dict(repo.ui.configitems('rhodecode_extras'))
171 171 if 'username' in extras:
172 172 username = extras['username']
173 173 repository = extras['repository']
174 174 scm = extras['scm']
175 175 make_lock = extras['make_lock']
176 176 locked_by = extras['locked_by']
177 177 ip = extras['ip']
178 178 elif 'username' in rc_extras:
179 179 username = rc_extras['username']
180 180 repository = rc_extras['repository']
181 181 scm = rc_extras['scm']
182 182 make_lock = rc_extras['make_lock']
183 183 locked_by = rc_extras['locked_by']
184 184 ip = rc_extras['ip']
185 185 else:
186 186 raise Exception('Missing data in repo.ui and os.environ')
187 187 user = User.get_by_username(username)
188 188 action = 'pull'
189 189 action_logger(user, action, repository, ip, commit=True)
190 190 # extension hook call
191 191 from rhodecode import EXTENSIONS
192 192 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
193 193
194 194 if isfunction(callback):
195 195 kw = {}
196 196 kw.update(extras)
197 197 callback(**kw)
198 198
199 199 if make_lock is True:
200 200 Repository.lock(Repository.get_by_repo_name(repository), user.user_id)
201 201 #msg = 'Made lock on repo `%s`' % repository
202 202 #sys.stdout.write(msg)
203 203
204 204 if locked_by[0]:
205 205 locked_by = User.get(locked_by[0]).username
206 206 _http_ret = HTTPLockedRC(repository, locked_by)
207 207 if str(_http_ret.code).startswith('2'):
208 208 #2xx Codes don't raise exceptions
209 209 sys.stdout.write(_http_ret.title)
210 210 return 0
211 211
212 212
213 213 def log_push_action(ui, repo, **kwargs):
214 214 """
215 215 Maps user last push action to new changeset id, from mercurial
216 216
217 217 :param ui:
218 218 :param repo: repo object containing the `ui` object
219 219 """
220 220
221 221 try:
222 222 rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}"))
223 223 except:
224 224 rc_extras = {}
225 225
226 226 extras = dict(repo.ui.configitems('rhodecode_extras'))
227 227 if 'username' in extras:
228 228 username = extras['username']
229 229 repository = extras['repository']
230 230 scm = extras['scm']
231 231 make_lock = extras['make_lock']
232 232 locked_by = extras['locked_by']
233 233 action = extras['action']
234 234 elif 'username' in rc_extras:
235 235 username = rc_extras['username']
236 236 repository = rc_extras['repository']
237 237 scm = rc_extras['scm']
238 238 make_lock = rc_extras['make_lock']
239 239 locked_by = rc_extras['locked_by']
240 240 action = extras['action']
241 241 else:
242 242 raise Exception('Missing data in repo.ui and os.environ')
243 243
244 244 action = action + ':%s'
245 245
246 246 if scm == 'hg':
247 247 node = kwargs['node']
248 248
249 249 def get_revs(repo, rev_opt):
250 250 if rev_opt:
251 251 revs = revrange(repo, rev_opt)
252 252
253 253 if len(revs) == 0:
254 254 return (nullrev, nullrev)
255 255 return (max(revs), min(revs))
256 256 else:
257 257 return (len(repo) - 1, 0)
258 258
259 259 stop, start = get_revs(repo, [node + ':'])
260 260 h = binascii.hexlify
261 261 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
262 262 elif scm == 'git':
263 263 revs = kwargs.get('_git_revs', [])
264 264 if '_git_revs' in kwargs:
265 265 kwargs.pop('_git_revs')
266 266
267 267 action = action % ','.join(revs)
268 268
269 269 action_logger(username, action, repository, extras['ip'], commit=True)
270 270
271 271 # extension hook call
272 272 from rhodecode import EXTENSIONS
273 273 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
274 274 if isfunction(callback):
275 275 kw = {'pushed_revs': revs}
276 276 kw.update(extras)
277 277 callback(**kw)
278 278
279 279 if make_lock is False:
280 280 Repository.unlock(Repository.get_by_repo_name(repository))
281 281 msg = 'Released lock on repo `%s`\n' % repository
282 282 sys.stdout.write(msg)
283 283
284 284 if locked_by[0]:
285 285 locked_by = User.get(locked_by[0]).username
286 286 _http_ret = HTTPLockedRC(repository, locked_by)
287 287 if str(_http_ret.code).startswith('2'):
288 288 #2xx Codes don't raise exceptions
289 289 sys.stdout.write(_http_ret.title)
290 290
291 291 return 0
292 292
293 293
294 294 def log_create_repository(repository_dict, created_by, **kwargs):
295 295 """
296 296 Post create repository Hook. This is a dummy function for admins to re-use
297 297 if needed. It's taken from rhodecode-extensions module and executed
298 298 if present
299 299
300 300 :param repository: dict dump of repository object
301 301 :param created_by: username who created repository
302 302
303 303 available keys of repository_dict:
304 304
305 305 'repo_type',
306 306 'description',
307 307 'private',
308 308 'created_on',
309 309 'enable_downloads',
310 310 'repo_id',
311 311 'user_id',
312 312 'enable_statistics',
313 313 'clone_uri',
314 314 'fork_id',
315 315 'group_id',
316 316 'repo_name'
317 317
318 318 """
319 319 from rhodecode import EXTENSIONS
320 320 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
321 321 if isfunction(callback):
322 322 kw = {}
323 323 kw.update(repository_dict)
324 324 kw.update({'created_by': created_by})
325 325 kw.update(kwargs)
326 326 return callback(**kw)
327 327
328 328 return 0
329 329
330 330
331 331 def log_delete_repository(repository_dict, deleted_by, **kwargs):
332 332 """
333 333 Post delete repository Hook. This is a dummy function for admins to re-use
334 334 if needed. It's taken from rhodecode-extensions module and executed
335 335 if present
336 336
337 337 :param repository: dict dump of repository object
338 338 :param deleted_by: username who deleted the repository
339 339
340 340 available keys of repository_dict:
341 341
342 342 'repo_type',
343 343 'description',
344 344 'private',
345 345 'created_on',
346 346 'enable_downloads',
347 347 'repo_id',
348 348 'user_id',
349 349 'enable_statistics',
350 350 'clone_uri',
351 351 'fork_id',
352 352 'group_id',
353 353 'repo_name'
354 354
355 355 """
356 356 from rhodecode import EXTENSIONS
357 357 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
358 358 if isfunction(callback):
359 359 kw = {}
360 360 kw.update(repository_dict)
361 361 kw.update({'deleted_by': deleted_by,
362 362 'deleted_on': time.time()})
363 363 kw.update(kwargs)
364 364 return callback(**kw)
365 365
366 366 return 0
367 367
368 368
369 369 handle_git_pre_receive = (lambda repo_path, revs, env:
370 370 handle_git_receive(repo_path, revs, env, hook_type='pre'))
371 371 handle_git_post_receive = (lambda repo_path, revs, env:
372 372 handle_git_receive(repo_path, revs, env, hook_type='post'))
373 373
374 374
375 375 def handle_git_receive(repo_path, revs, env, hook_type='post'):
376 376 """
377 377 A really hacky method that is runned by git post-receive hook and logs
378 378 an push action together with pushed revisions. It's executed by subprocess
379 379 thus needs all info to be able to create a on the fly pylons enviroment,
380 380 connect to database and run the logging code. Hacky as sh*t but works.
381 381
382 382 :param repo_path:
383 383 :type repo_path:
384 384 :param revs:
385 385 :type revs:
386 386 :param env:
387 387 :type env:
388 388 """
389 389 from paste.deploy import appconfig
390 390 from sqlalchemy import engine_from_config
391 391 from rhodecode.config.environment import load_environment
392 392 from rhodecode.model import init_model
393 393 from rhodecode.model.db import RhodeCodeUi
394 394 from rhodecode.lib.utils import make_ui
395 395 extras = json.loads(env['RHODECODE_EXTRAS'])
396 396
397 397 path, ini_name = os.path.split(extras['config'])
398 398 conf = appconfig('config:%s' % ini_name, relative_to=path)
399 399 load_environment(conf.global_conf, conf.local_conf)
400 400
401 401 engine = engine_from_config(conf, 'sqlalchemy.db1.')
402 402 init_model(engine)
403 403
404 404 baseui = make_ui('db')
405 405 # fix if it's not a bare repo
406 406 if repo_path.endswith(os.sep + '.git'):
407 407 repo_path = repo_path[:-5]
408 408
409 409 repo = Repository.get_by_full_path(repo_path)
410 410 if not repo:
411 411 raise OSError('Repository %s not found in database'
412 412 % (safe_str(repo_path)))
413 413
414 414 _hooks = dict(baseui.configitems('hooks')) or {}
415 415
416 416 for k, v in extras.items():
417 417 baseui.setconfig('rhodecode_extras', k, v)
418 418 if hook_type == 'pre':
419 419 repo = repo.scm_instance
420 420 else:
421 421 #post push shouldn't use the cached instance never
422 repo = repo.scm_instance_no_cache
422 repo = repo.scm_instance_no_cache()
423 423
424 424 repo.ui = baseui
425 425
426 426 if hook_type == 'pre':
427 427 pre_push(baseui, repo)
428 428
429 429 # if push hook is enabled via web interface
430 430 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
431 431
432 432 rev_data = []
433 433 for l in revs:
434 434 old_rev, new_rev, ref = l.split(' ')
435 435 _ref_data = ref.split('/')
436 436 if _ref_data[1] in ['tags', 'heads']:
437 437 rev_data.append({'old_rev': old_rev,
438 438 'new_rev': new_rev,
439 439 'ref': ref,
440 440 'type': _ref_data[1],
441 441 'name': _ref_data[2].strip()})
442 442
443 443 git_revs = []
444 444 for push_ref in rev_data:
445 445 _type = push_ref['type']
446 446 if _type == 'heads':
447 447 if push_ref['old_rev'] == EmptyChangeset().raw_id:
448 448 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
449 449 heads = repo.run_git_command(cmd)[0]
450 450 heads = heads.replace(push_ref['ref'], '')
451 451 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
452 452 heads.splitlines()))
453 453 cmd = (('log %(new_rev)s' % push_ref) +
454 454 ' --reverse --pretty=format:"%H" --not ' + heads)
455 455 git_revs += repo.run_git_command(cmd)[0].splitlines()
456 456
457 457 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
458 458 #delete branch case
459 459 git_revs += ['delete_branch=>%s' % push_ref['name']]
460 460 else:
461 461 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
462 462 ' --reverse --pretty=format:"%H"')
463 463 git_revs += repo.run_git_command(cmd)[0].splitlines()
464 464
465 465 elif _type == 'tags':
466 466 git_revs += ['tag=>%s' % push_ref['name']]
467 467
468 468 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,2057 +1,2056 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194 @classmethod
195 195 def get_by_name(cls, key):
196 196 return cls.query()\
197 197 .filter(cls.app_settings_name == key).scalar()
198 198
199 199 @classmethod
200 200 def get_by_name_or_create(cls, key):
201 201 res = cls.get_by_name(key)
202 202 if not res:
203 203 res = cls(key)
204 204 return res
205 205
206 206 @classmethod
207 207 def get_app_settings(cls, cache=False):
208 208
209 209 ret = cls.query()
210 210
211 211 if cache:
212 212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 213
214 214 if not ret:
215 215 raise Exception('Could not get application settings !')
216 216 settings = {}
217 217 for each in ret:
218 218 settings['rhodecode_' + each.app_settings_name] = \
219 219 each.app_settings_value
220 220
221 221 return settings
222 222
223 223 @classmethod
224 224 def get_ldap_settings(cls, cache=False):
225 225 ret = cls.query()\
226 226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 227 fd = {}
228 228 for row in ret:
229 229 fd.update({row.app_settings_name: row.app_settings_value})
230 230
231 231 return fd
232 232
233 233 @classmethod
234 234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 235 ret = cls.query()\
236 236 .filter(cls.app_settings_name.startswith('default_')).all()
237 237 fd = {}
238 238 for row in ret:
239 239 key = row.app_settings_name
240 240 if strip_prefix:
241 241 key = remove_prefix(key, prefix='default_')
242 242 fd.update({key: row.app_settings_value})
243 243
244 244 return fd
245 245
246 246
247 247 class RhodeCodeUi(Base, BaseModel):
248 248 __tablename__ = 'rhodecode_ui'
249 249 __table_args__ = (
250 250 UniqueConstraint('ui_key'),
251 251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 252 'mysql_charset': 'utf8'}
253 253 )
254 254
255 255 HOOK_UPDATE = 'changegroup.update'
256 256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 257 HOOK_PUSH = 'changegroup.push_logger'
258 258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 259 HOOK_PULL = 'outgoing.pull_logger'
260 260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261 261
262 262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267 267
268 268 @classmethod
269 269 def get_by_key(cls, key):
270 270 return cls.query().filter(cls.ui_key == key).scalar()
271 271
272 272 @classmethod
273 273 def get_builtin_hooks(cls):
274 274 q = cls.query()
275 275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 278 return q.all()
279 279
280 280 @classmethod
281 281 def get_custom_hooks(cls):
282 282 q = cls.query()
283 283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 286 q = q.filter(cls.ui_section == 'hooks')
287 287 return q.all()
288 288
289 289 @classmethod
290 290 def get_repos_location(cls):
291 291 return cls.get_by_key('/').ui_value
292 292
293 293 @classmethod
294 294 def create_or_update_hook(cls, key, val):
295 295 new_ui = cls.get_by_key(key) or cls()
296 296 new_ui.ui_section = 'hooks'
297 297 new_ui.ui_active = True
298 298 new_ui.ui_key = key
299 299 new_ui.ui_value = val
300 300
301 301 Session().add(new_ui)
302 302
303 303 def __repr__(self):
304 304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 305 self.ui_value)
306 306
307 307
308 308 class User(Base, BaseModel):
309 309 __tablename__ = 'users'
310 310 __table_args__ = (
311 311 UniqueConstraint('username'), UniqueConstraint('email'),
312 312 Index('u_username_idx', 'username'),
313 313 Index('u_email_idx', 'email'),
314 314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 315 'mysql_charset': 'utf8'}
316 316 )
317 317 DEFAULT_USER = 'default'
318 318 DEFAULT_PERMISSIONS = [
319 319 'hg.register.manual_activate', 'hg.create.repository',
320 320 'hg.fork.repository', 'repository.read', 'group.read'
321 321 ]
322 322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334 334
335 335 user_log = relationship('UserLog')
336 336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337 337
338 338 repositories = relationship('Repository')
339 339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341 341
342 342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344 344
345 345 group_member = relationship('UserGroupMember', cascade='all')
346 346
347 347 notifications = relationship('UserNotification', cascade='all')
348 348 # notifications assigned to this user
349 349 user_created_notifications = relationship('Notification', cascade='all')
350 350 # comments created by this user
351 351 user_comments = relationship('ChangesetComment', cascade='all')
352 352 #extra emails for this user
353 353 user_emails = relationship('UserEmailMap', cascade='all')
354 354
355 355 @hybrid_property
356 356 def email(self):
357 357 return self._email
358 358
359 359 @email.setter
360 360 def email(self, val):
361 361 self._email = val.lower() if val else None
362 362
363 363 @property
364 364 def firstname(self):
365 365 # alias for future
366 366 return self.name
367 367
368 368 @property
369 369 def emails(self):
370 370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 371 return [self.email] + [x.email for x in other]
372 372
373 373 @property
374 374 def ip_addresses(self):
375 375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 376 return [x.ip_addr for x in ret]
377 377
378 378 @property
379 379 def username_and_name(self):
380 380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381 381
382 382 @property
383 383 def full_name(self):
384 384 return '%s %s' % (self.firstname, self.lastname)
385 385
386 386 @property
387 387 def full_name_or_username(self):
388 388 return ('%s %s' % (self.firstname, self.lastname)
389 389 if (self.firstname and self.lastname) else self.username)
390 390
391 391 @property
392 392 def full_contact(self):
393 393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394 394
395 395 @property
396 396 def short_contact(self):
397 397 return '%s %s' % (self.firstname, self.lastname)
398 398
399 399 @property
400 400 def is_admin(self):
401 401 return self.admin
402 402
403 403 @property
404 404 def AuthUser(self):
405 405 """
406 406 Returns instance of AuthUser for this user
407 407 """
408 408 from rhodecode.lib.auth import AuthUser
409 409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 410 username=self.username)
411 411
412 412 def __unicode__(self):
413 413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 414 self.user_id, self.username)
415 415
416 416 @classmethod
417 417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 418 if case_insensitive:
419 419 q = cls.query().filter(cls.username.ilike(username))
420 420 else:
421 421 q = cls.query().filter(cls.username == username)
422 422
423 423 if cache:
424 424 q = q.options(FromCache(
425 425 "sql_cache_short",
426 426 "get_user_%s" % _hash_key(username)
427 427 )
428 428 )
429 429 return q.scalar()
430 430
431 431 @classmethod
432 432 def get_by_api_key(cls, api_key, cache=False):
433 433 q = cls.query().filter(cls.api_key == api_key)
434 434
435 435 if cache:
436 436 q = q.options(FromCache("sql_cache_short",
437 437 "get_api_key_%s" % api_key))
438 438 return q.scalar()
439 439
440 440 @classmethod
441 441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 442 if case_insensitive:
443 443 q = cls.query().filter(cls.email.ilike(email))
444 444 else:
445 445 q = cls.query().filter(cls.email == email)
446 446
447 447 if cache:
448 448 q = q.options(FromCache("sql_cache_short",
449 449 "get_email_key_%s" % email))
450 450
451 451 ret = q.scalar()
452 452 if ret is None:
453 453 q = UserEmailMap.query()
454 454 # try fetching in alternate email map
455 455 if case_insensitive:
456 456 q = q.filter(UserEmailMap.email.ilike(email))
457 457 else:
458 458 q = q.filter(UserEmailMap.email == email)
459 459 q = q.options(joinedload(UserEmailMap.user))
460 460 if cache:
461 461 q = q.options(FromCache("sql_cache_short",
462 462 "get_email_map_key_%s" % email))
463 463 ret = getattr(q.scalar(), 'user', None)
464 464
465 465 return ret
466 466
467 467 @classmethod
468 468 def get_from_cs_author(cls, author):
469 469 """
470 470 Tries to get User objects out of commit author string
471 471
472 472 :param author:
473 473 """
474 474 from rhodecode.lib.helpers import email, author_name
475 475 # Valid email in the attribute passed, see if they're in the system
476 476 _email = email(author)
477 477 if _email:
478 478 user = cls.get_by_email(_email, case_insensitive=True)
479 479 if user:
480 480 return user
481 481 # Maybe we can match by username?
482 482 _author = author_name(author)
483 483 user = cls.get_by_username(_author, case_insensitive=True)
484 484 if user:
485 485 return user
486 486
487 487 def update_lastlogin(self):
488 488 """Update user lastlogin"""
489 489 self.last_login = datetime.datetime.now()
490 490 Session().add(self)
491 491 log.debug('updated user %s lastlogin' % self.username)
492 492
493 493 def get_api_data(self):
494 494 """
495 495 Common function for generating user related data for API
496 496 """
497 497 user = self
498 498 data = dict(
499 499 user_id=user.user_id,
500 500 username=user.username,
501 501 firstname=user.name,
502 502 lastname=user.lastname,
503 503 email=user.email,
504 504 emails=user.emails,
505 505 api_key=user.api_key,
506 506 active=user.active,
507 507 admin=user.admin,
508 508 ldap_dn=user.ldap_dn,
509 509 last_login=user.last_login,
510 510 ip_addresses=user.ip_addresses
511 511 )
512 512 return data
513 513
514 514 def __json__(self):
515 515 data = dict(
516 516 full_name=self.full_name,
517 517 full_name_or_username=self.full_name_or_username,
518 518 short_contact=self.short_contact,
519 519 full_contact=self.full_contact
520 520 )
521 521 data.update(self.get_api_data())
522 522 return data
523 523
524 524
525 525 class UserEmailMap(Base, BaseModel):
526 526 __tablename__ = 'user_email_map'
527 527 __table_args__ = (
528 528 Index('uem_email_idx', 'email'),
529 529 UniqueConstraint('email'),
530 530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 531 'mysql_charset': 'utf8'}
532 532 )
533 533 __mapper_args__ = {}
534 534
535 535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 538 user = relationship('User', lazy='joined')
539 539
540 540 @validates('_email')
541 541 def validate_email(self, key, email):
542 542 # check if this email is not main one
543 543 main_email = Session().query(User).filter(User.email == email).scalar()
544 544 if main_email is not None:
545 545 raise AttributeError('email %s is present is user table' % email)
546 546 return email
547 547
548 548 @hybrid_property
549 549 def email(self):
550 550 return self._email
551 551
552 552 @email.setter
553 553 def email(self, val):
554 554 self._email = val.lower() if val else None
555 555
556 556
557 557 class UserIpMap(Base, BaseModel):
558 558 __tablename__ = 'user_ip_map'
559 559 __table_args__ = (
560 560 UniqueConstraint('user_id', 'ip_addr'),
561 561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 562 'mysql_charset': 'utf8'}
563 563 )
564 564 __mapper_args__ = {}
565 565
566 566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 570 user = relationship('User', lazy='joined')
571 571
572 572 @classmethod
573 573 def _get_ip_range(cls, ip_addr):
574 574 from rhodecode.lib import ipaddr
575 575 net = ipaddr.IPNetwork(address=ip_addr)
576 576 return [str(net.network), str(net.broadcast)]
577 577
578 578 def __json__(self):
579 579 return dict(
580 580 ip_addr=self.ip_addr,
581 581 ip_range=self._get_ip_range(self.ip_addr)
582 582 )
583 583
584 584
585 585 class UserLog(Base, BaseModel):
586 586 __tablename__ = 'user_logs'
587 587 __table_args__ = (
588 588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 589 'mysql_charset': 'utf8'},
590 590 )
591 591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599 599
600 600 @property
601 601 def action_as_day(self):
602 602 return datetime.date(*self.action_date.timetuple()[:3])
603 603
604 604 user = relationship('User')
605 605 repository = relationship('Repository', cascade='')
606 606
607 607
608 608 class UserGroup(Base, BaseModel):
609 609 __tablename__ = 'users_groups'
610 610 __table_args__ = (
611 611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 612 'mysql_charset': 'utf8'},
613 613 )
614 614
615 615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619 619
620 620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623 623
624 624 def __unicode__(self):
625 625 return u'<userGroup(%s)>' % (self.users_group_name)
626 626
627 627 @classmethod
628 628 def get_by_group_name(cls, group_name, cache=False,
629 629 case_insensitive=False):
630 630 if case_insensitive:
631 631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 632 else:
633 633 q = cls.query().filter(cls.users_group_name == group_name)
634 634 if cache:
635 635 q = q.options(FromCache(
636 636 "sql_cache_short",
637 637 "get_user_%s" % _hash_key(group_name)
638 638 )
639 639 )
640 640 return q.scalar()
641 641
642 642 @classmethod
643 643 def get(cls, users_group_id, cache=False):
644 644 users_group = cls.query()
645 645 if cache:
646 646 users_group = users_group.options(FromCache("sql_cache_short",
647 647 "get_users_group_%s" % users_group_id))
648 648 return users_group.get(users_group_id)
649 649
650 650 def get_api_data(self):
651 651 users_group = self
652 652
653 653 data = dict(
654 654 users_group_id=users_group.users_group_id,
655 655 group_name=users_group.users_group_name,
656 656 active=users_group.users_group_active,
657 657 )
658 658
659 659 return data
660 660
661 661
662 662 class UserGroupMember(Base, BaseModel):
663 663 __tablename__ = 'users_groups_members'
664 664 __table_args__ = (
665 665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 666 'mysql_charset': 'utf8'},
667 667 )
668 668
669 669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672 672
673 673 user = relationship('User', lazy='joined')
674 674 users_group = relationship('UserGroup')
675 675
676 676 def __init__(self, gr_id='', u_id=''):
677 677 self.users_group_id = gr_id
678 678 self.user_id = u_id
679 679
680 680
681 681 class RepositoryField(Base, BaseModel):
682 682 __tablename__ = 'repositories_fields'
683 683 __table_args__ = (
684 684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 686 'mysql_charset': 'utf8'},
687 687 )
688 688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689 689
690 690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698 698
699 699 repository = relationship('Repository')
700 700
701 701 @property
702 702 def field_key_prefixed(self):
703 703 return 'ex_%s' % self.field_key
704 704
705 705 @classmethod
706 706 def un_prefix_key(cls, key):
707 707 if key.startswith(cls.PREFIX):
708 708 return key[len(cls.PREFIX):]
709 709 return key
710 710
711 711 @classmethod
712 712 def get_by_key_name(cls, key, repo):
713 713 row = cls.query()\
714 714 .filter(cls.repository == repo)\
715 715 .filter(cls.field_key == key).scalar()
716 716 return row
717 717
718 718
719 719 class Repository(Base, BaseModel):
720 720 __tablename__ = 'repositories'
721 721 __table_args__ = (
722 722 UniqueConstraint('repo_name'),
723 723 Index('r_repo_name_idx', 'repo_name'),
724 724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 725 'mysql_charset': 'utf8'},
726 726 )
727 727
728 728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743 743
744 744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746 746
747 747 user = relationship('User')
748 748 fork = relationship('Repository', remote_side=repo_id)
749 749 group = relationship('RepoGroup')
750 750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 752 stats = relationship('Statistics', cascade='all', uselist=False)
753 753
754 754 followers = relationship('UserFollowing',
755 755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 756 cascade='all')
757 757 extra_fields = relationship('RepositoryField',
758 758 cascade="all, delete, delete-orphan")
759 759
760 760 logs = relationship('UserLog')
761 761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762 762
763 763 pull_requests_org = relationship('PullRequest',
764 764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 765 cascade="all, delete, delete-orphan")
766 766
767 767 pull_requests_other = relationship('PullRequest',
768 768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 769 cascade="all, delete, delete-orphan")
770 770
771 771 def __unicode__(self):
772 772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 773 self.repo_name)
774 774
775 775 @hybrid_property
776 776 def locked(self):
777 777 # always should return [user_id, timelocked]
778 778 if self._locked:
779 779 _lock_info = self._locked.split(':')
780 780 return int(_lock_info[0]), _lock_info[1]
781 781 return [None, None]
782 782
783 783 @locked.setter
784 784 def locked(self, val):
785 785 if val and isinstance(val, (list, tuple)):
786 786 self._locked = ':'.join(map(str, val))
787 787 else:
788 788 self._locked = None
789 789
790 790 @hybrid_property
791 791 def changeset_cache(self):
792 792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 793 dummy = EmptyChangeset().__json__()
794 794 if not self._changeset_cache:
795 795 return dummy
796 796 try:
797 797 return json.loads(self._changeset_cache)
798 798 except TypeError:
799 799 return dummy
800 800
801 801 @changeset_cache.setter
802 802 def changeset_cache(self, val):
803 803 try:
804 804 self._changeset_cache = json.dumps(val)
805 805 except:
806 806 log.error(traceback.format_exc())
807 807
808 808 @classmethod
809 809 def url_sep(cls):
810 810 return URL_SEP
811 811
812 812 @classmethod
813 813 def normalize_repo_name(cls, repo_name):
814 814 """
815 815 Normalizes os specific repo_name to the format internally stored inside
816 816 dabatabase using URL_SEP
817 817
818 818 :param cls:
819 819 :param repo_name:
820 820 """
821 821 return cls.url_sep().join(repo_name.split(os.sep))
822 822
823 823 @classmethod
824 824 def get_by_repo_name(cls, repo_name):
825 825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 826 q = q.options(joinedload(Repository.fork))\
827 827 .options(joinedload(Repository.user))\
828 828 .options(joinedload(Repository.group))
829 829 return q.scalar()
830 830
831 831 @classmethod
832 832 def get_by_full_path(cls, repo_full_path):
833 833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 834 repo_name = cls.normalize_repo_name(repo_name)
835 835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836 836
837 837 @classmethod
838 838 def get_repo_forks(cls, repo_id):
839 839 return cls.query().filter(Repository.fork_id == repo_id)
840 840
841 841 @classmethod
842 842 def base_path(cls):
843 843 """
844 844 Returns base path when all repos are stored
845 845
846 846 :param cls:
847 847 """
848 848 q = Session().query(RhodeCodeUi)\
849 849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 851 return q.one().ui_value
852 852
853 853 @property
854 854 def forks(self):
855 855 """
856 856 Return forks of this repo
857 857 """
858 858 return Repository.get_repo_forks(self.repo_id)
859 859
860 860 @property
861 861 def parent(self):
862 862 """
863 863 Returns fork parent
864 864 """
865 865 return self.fork
866 866
867 867 @property
868 868 def just_name(self):
869 869 return self.repo_name.split(Repository.url_sep())[-1]
870 870
871 871 @property
872 872 def groups_with_parents(self):
873 873 groups = []
874 874 if self.group is None:
875 875 return groups
876 876
877 877 cur_gr = self.group
878 878 groups.insert(0, cur_gr)
879 879 while 1:
880 880 gr = getattr(cur_gr, 'parent_group', None)
881 881 cur_gr = cur_gr.parent_group
882 882 if gr is None:
883 883 break
884 884 groups.insert(0, gr)
885 885
886 886 return groups
887 887
888 888 @property
889 889 def groups_and_repo(self):
890 890 return self.groups_with_parents, self.just_name
891 891
892 892 @LazyProperty
893 893 def repo_path(self):
894 894 """
895 895 Returns base full path for that repository means where it actually
896 896 exists on a filesystem
897 897 """
898 898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 899 Repository.url_sep())
900 900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 901 return q.one().ui_value
902 902
903 903 @property
904 904 def repo_full_path(self):
905 905 p = [self.repo_path]
906 906 # we need to split the name by / since this is how we store the
907 907 # names in the database, but that eventually needs to be converted
908 908 # into a valid system path
909 909 p += self.repo_name.split(Repository.url_sep())
910 910 return os.path.join(*p)
911 911
912 912 @property
913 913 def cache_keys(self):
914 914 """
915 915 Returns associated cache keys for that repo
916 916 """
917 917 return CacheInvalidation.query()\
918 918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 919 .order_by(CacheInvalidation.cache_key)\
920 920 .all()
921 921
922 922 def get_new_name(self, repo_name):
923 923 """
924 924 returns new full repository name based on assigned group and new new
925 925
926 926 :param group_name:
927 927 """
928 928 path_prefix = self.group.full_path_splitted if self.group else []
929 929 return Repository.url_sep().join(path_prefix + [repo_name])
930 930
931 931 @property
932 932 def _ui(self):
933 933 """
934 934 Creates an db based ui object for this repository
935 935 """
936 936 from rhodecode.lib.utils import make_ui
937 937 return make_ui('db', clear_session=False)
938 938
939 939 @classmethod
940 940 def inject_ui(cls, repo, extras={}):
941 941 repo.inject_ui(extras)
942 942
943 943 @classmethod
944 944 def is_valid(cls, repo_name):
945 945 """
946 946 returns True if given repo name is a valid filesystem repository
947 947
948 948 :param cls:
949 949 :param repo_name:
950 950 """
951 951 from rhodecode.lib.utils import is_valid_repo
952 952
953 953 return is_valid_repo(repo_name, cls.base_path())
954 954
955 955 def get_api_data(self):
956 956 """
957 957 Common function for generating repo api data
958 958
959 959 """
960 960 repo = self
961 961 data = dict(
962 962 repo_id=repo.repo_id,
963 963 repo_name=repo.repo_name,
964 964 repo_type=repo.repo_type,
965 965 clone_uri=repo.clone_uri,
966 966 private=repo.private,
967 967 created_on=repo.created_on,
968 968 description=repo.description,
969 969 landing_rev=repo.landing_rev,
970 970 owner=repo.user.username,
971 971 fork_of=repo.fork.repo_name if repo.fork else None,
972 972 enable_statistics=repo.enable_statistics,
973 973 enable_locking=repo.enable_locking,
974 974 enable_downloads=repo.enable_downloads,
975 975 last_changeset=repo.changeset_cache,
976 976 locked_by=User.get(self.locked[0]).get_api_data() \
977 977 if self.locked[0] else None,
978 978 locked_date=time_to_datetime(self.locked[1]) \
979 979 if self.locked[1] else None
980 980 )
981 981 rc_config = RhodeCodeSetting.get_app_settings()
982 982 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
983 983 if repository_fields:
984 984 for f in self.extra_fields:
985 985 data[f.field_key_prefixed] = f.field_value
986 986
987 987 return data
988 988
989 989 @classmethod
990 990 def lock(cls, repo, user_id):
991 991 repo.locked = [user_id, time.time()]
992 992 Session().add(repo)
993 993 Session().commit()
994 994
995 995 @classmethod
996 996 def unlock(cls, repo):
997 997 repo.locked = None
998 998 Session().add(repo)
999 999 Session().commit()
1000 1000
1001 1001 @classmethod
1002 1002 def getlock(cls, repo):
1003 1003 return repo.locked
1004 1004
1005 1005 @property
1006 1006 def last_db_change(self):
1007 1007 return self.updated_on
1008 1008
1009 1009 def clone_url(self, **override):
1010 1010 from pylons import url
1011 1011 from urlparse import urlparse
1012 1012 import urllib
1013 1013 parsed_url = urlparse(url('home', qualified=True))
1014 1014 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1015 1015 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1016 1016 args = {
1017 1017 'user': '',
1018 1018 'pass': '',
1019 1019 'scheme': parsed_url.scheme,
1020 1020 'netloc': parsed_url.netloc,
1021 1021 'prefix': decoded_path,
1022 1022 'path': self.repo_name
1023 1023 }
1024 1024
1025 1025 args.update(override)
1026 1026 return default_clone_uri % args
1027 1027
1028 1028 #==========================================================================
1029 1029 # SCM PROPERTIES
1030 1030 #==========================================================================
1031 1031
1032 1032 def get_changeset(self, rev=None):
1033 1033 return get_changeset_safe(self.scm_instance, rev)
1034 1034
1035 1035 def get_landing_changeset(self):
1036 1036 """
1037 1037 Returns landing changeset, or if that doesn't exist returns the tip
1038 1038 """
1039 1039 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1040 1040 return cs
1041 1041
1042 1042 def update_changeset_cache(self, cs_cache=None):
1043 1043 """
1044 1044 Update cache of last changeset for repository, keys should be::
1045 1045
1046 1046 short_id
1047 1047 raw_id
1048 1048 revision
1049 1049 message
1050 1050 date
1051 1051 author
1052 1052
1053 1053 :param cs_cache:
1054 1054 """
1055 1055 from rhodecode.lib.vcs.backends.base import BaseChangeset
1056 1056 if cs_cache is None:
1057 1057 cs_cache = EmptyChangeset()
1058 1058 # use no-cache version here
1059 scm_repo = self.scm_instance_no_cache
1059 scm_repo = self.scm_instance_no_cache()
1060 1060 if scm_repo:
1061 1061 cs_cache = scm_repo.get_changeset()
1062 1062
1063 1063 if isinstance(cs_cache, BaseChangeset):
1064 1064 cs_cache = cs_cache.__json__()
1065 1065
1066 1066 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1067 1067 _default = datetime.datetime.fromtimestamp(0)
1068 1068 last_change = cs_cache.get('date') or _default
1069 1069 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1070 1070 self.updated_on = last_change
1071 1071 self.changeset_cache = cs_cache
1072 1072 Session().add(self)
1073 1073 Session().commit()
1074 1074 else:
1075 1075 log.debug('Skipping repo:%s already with latest changes' % self)
1076 1076
1077 1077 @property
1078 1078 def tip(self):
1079 1079 return self.get_changeset('tip')
1080 1080
1081 1081 @property
1082 1082 def author(self):
1083 1083 return self.tip.author
1084 1084
1085 1085 @property
1086 1086 def last_change(self):
1087 1087 return self.scm_instance.last_change
1088 1088
1089 1089 def get_comments(self, revisions=None):
1090 1090 """
1091 1091 Returns comments for this repository grouped by revisions
1092 1092
1093 1093 :param revisions: filter query by revisions only
1094 1094 """
1095 1095 cmts = ChangesetComment.query()\
1096 1096 .filter(ChangesetComment.repo == self)
1097 1097 if revisions:
1098 1098 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1099 1099 grouped = defaultdict(list)
1100 1100 for cmt in cmts.all():
1101 1101 grouped[cmt.revision].append(cmt)
1102 1102 return grouped
1103 1103
1104 1104 def statuses(self, revisions=None):
1105 1105 """
1106 1106 Returns statuses for this repository
1107 1107
1108 1108 :param revisions: list of revisions to get statuses for
1109 1109 :type revisions: list
1110 1110 """
1111 1111
1112 1112 statuses = ChangesetStatus.query()\
1113 1113 .filter(ChangesetStatus.repo == self)\
1114 1114 .filter(ChangesetStatus.version == 0)
1115 1115 if revisions:
1116 1116 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1117 1117 grouped = {}
1118 1118
1119 1119 #maybe we have open new pullrequest without a status ?
1120 1120 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1121 1121 status_lbl = ChangesetStatus.get_status_lbl(stat)
1122 1122 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1123 1123 for rev in pr.revisions:
1124 1124 pr_id = pr.pull_request_id
1125 1125 pr_repo = pr.other_repo.repo_name
1126 1126 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1127 1127
1128 1128 for stat in statuses.all():
1129 1129 pr_id = pr_repo = None
1130 1130 if stat.pull_request:
1131 1131 pr_id = stat.pull_request.pull_request_id
1132 1132 pr_repo = stat.pull_request.other_repo.repo_name
1133 1133 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1134 1134 pr_id, pr_repo]
1135 1135 return grouped
1136 1136
1137 1137 def _repo_size(self):
1138 1138 from rhodecode.lib import helpers as h
1139 1139 log.debug('calculating repository size...')
1140 1140 return h.format_byte_size(self.scm_instance.size)
1141 1141
1142 1142 #==========================================================================
1143 1143 # SCM CACHE INSTANCE
1144 1144 #==========================================================================
1145 1145
1146 1146 @property
1147 1147 def invalidate(self):
1148 1148 return CacheInvalidation.invalidate(self.repo_name)
1149 1149
1150 1150 def set_invalidate(self):
1151 1151 """
1152 1152 set a cache for invalidation for this instance
1153 1153 """
1154 1154 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1155 1155
1156 @LazyProperty
1157 1156 def scm_instance_no_cache(self):
1158 1157 return self.__get_instance()
1159 1158
1160 1159 @LazyProperty
1161 1160 def scm_instance(self):
1162 1161 import rhodecode
1163 1162 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1164 1163 if full_cache:
1165 1164 return self.scm_instance_cached()
1166 1165 return self.__get_instance()
1167 1166
1168 1167 def scm_instance_cached(self, cache_map=None):
1169 1168 @cache_region('long_term')
1170 1169 def _c(repo_name):
1171 1170 return self.__get_instance()
1172 1171 rn = self.repo_name
1173 1172 log.debug('Getting cached instance of repo')
1174 1173
1175 1174 if cache_map:
1176 1175 # get using prefilled cache_map
1177 1176 invalidate_repo = cache_map[self.repo_name]
1178 1177 if invalidate_repo:
1179 1178 invalidate_repo = (None if invalidate_repo.cache_active
1180 1179 else invalidate_repo)
1181 1180 else:
1182 1181 # get from invalidate
1183 1182 invalidate_repo = self.invalidate
1184 1183
1185 1184 if invalidate_repo is not None:
1186 1185 region_invalidate(_c, None, rn)
1187 1186 # update our cache
1188 1187 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1189 1188 return _c(rn)
1190 1189
1191 1190 def __get_instance(self):
1192 1191 repo_full_path = self.repo_full_path
1193 1192 try:
1194 1193 alias = get_scm(repo_full_path)[0]
1195 1194 log.debug('Creating instance of %s repository from %s'
1196 1195 % (alias, repo_full_path))
1197 1196 backend = get_backend(alias)
1198 1197 except VCSError:
1199 1198 log.error(traceback.format_exc())
1200 1199 log.error('Perhaps this repository is in db and not in '
1201 1200 'filesystem run rescan repositories with '
1202 1201 '"destroy old data " option from admin panel')
1203 1202 return
1204 1203
1205 1204 if alias == 'hg':
1206 1205
1207 1206 repo = backend(safe_str(repo_full_path), create=False,
1208 1207 baseui=self._ui)
1209 1208 # skip hidden web repository
1210 1209 if repo._get_hidden():
1211 1210 return
1212 1211 else:
1213 1212 repo = backend(repo_full_path, create=False)
1214 1213
1215 1214 return repo
1216 1215
1217 1216
1218 1217 class RepoGroup(Base, BaseModel):
1219 1218 __tablename__ = 'groups'
1220 1219 __table_args__ = (
1221 1220 UniqueConstraint('group_name', 'group_parent_id'),
1222 1221 CheckConstraint('group_id != group_parent_id'),
1223 1222 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1224 1223 'mysql_charset': 'utf8'},
1225 1224 )
1226 1225 __mapper_args__ = {'order_by': 'group_name'}
1227 1226
1228 1227 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1229 1228 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1230 1229 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1231 1230 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1232 1231 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1233 1232
1234 1233 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1235 1234 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1236 1235
1237 1236 parent_group = relationship('RepoGroup', remote_side=group_id)
1238 1237
1239 1238 def __init__(self, group_name='', parent_group=None):
1240 1239 self.group_name = group_name
1241 1240 self.parent_group = parent_group
1242 1241
1243 1242 def __unicode__(self):
1244 1243 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1245 1244 self.group_name)
1246 1245
1247 1246 @classmethod
1248 1247 def groups_choices(cls, groups=None, show_empty_group=True):
1249 1248 from webhelpers.html import literal as _literal
1250 1249 if not groups:
1251 1250 groups = cls.query().all()
1252 1251
1253 1252 repo_groups = []
1254 1253 if show_empty_group:
1255 1254 repo_groups = [('-1', '-- no parent --')]
1256 1255 sep = ' &raquo; '
1257 1256 _name = lambda k: _literal(sep.join(k))
1258 1257
1259 1258 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1260 1259 for x in groups])
1261 1260
1262 1261 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1263 1262 return repo_groups
1264 1263
1265 1264 @classmethod
1266 1265 def url_sep(cls):
1267 1266 return URL_SEP
1268 1267
1269 1268 @classmethod
1270 1269 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1271 1270 if case_insensitive:
1272 1271 gr = cls.query()\
1273 1272 .filter(cls.group_name.ilike(group_name))
1274 1273 else:
1275 1274 gr = cls.query()\
1276 1275 .filter(cls.group_name == group_name)
1277 1276 if cache:
1278 1277 gr = gr.options(FromCache(
1279 1278 "sql_cache_short",
1280 1279 "get_group_%s" % _hash_key(group_name)
1281 1280 )
1282 1281 )
1283 1282 return gr.scalar()
1284 1283
1285 1284 @property
1286 1285 def parents(self):
1287 1286 parents_recursion_limit = 5
1288 1287 groups = []
1289 1288 if self.parent_group is None:
1290 1289 return groups
1291 1290 cur_gr = self.parent_group
1292 1291 groups.insert(0, cur_gr)
1293 1292 cnt = 0
1294 1293 while 1:
1295 1294 cnt += 1
1296 1295 gr = getattr(cur_gr, 'parent_group', None)
1297 1296 cur_gr = cur_gr.parent_group
1298 1297 if gr is None:
1299 1298 break
1300 1299 if cnt == parents_recursion_limit:
1301 1300 # this will prevent accidental infinit loops
1302 1301 log.error('group nested more than %s' %
1303 1302 parents_recursion_limit)
1304 1303 break
1305 1304
1306 1305 groups.insert(0, gr)
1307 1306 return groups
1308 1307
1309 1308 @property
1310 1309 def children(self):
1311 1310 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1312 1311
1313 1312 @property
1314 1313 def name(self):
1315 1314 return self.group_name.split(RepoGroup.url_sep())[-1]
1316 1315
1317 1316 @property
1318 1317 def full_path(self):
1319 1318 return self.group_name
1320 1319
1321 1320 @property
1322 1321 def full_path_splitted(self):
1323 1322 return self.group_name.split(RepoGroup.url_sep())
1324 1323
1325 1324 @property
1326 1325 def repositories(self):
1327 1326 return Repository.query()\
1328 1327 .filter(Repository.group == self)\
1329 1328 .order_by(Repository.repo_name)
1330 1329
1331 1330 @property
1332 1331 def repositories_recursive_count(self):
1333 1332 cnt = self.repositories.count()
1334 1333
1335 1334 def children_count(group):
1336 1335 cnt = 0
1337 1336 for child in group.children:
1338 1337 cnt += child.repositories.count()
1339 1338 cnt += children_count(child)
1340 1339 return cnt
1341 1340
1342 1341 return cnt + children_count(self)
1343 1342
1344 1343 def _recursive_objects(self, include_repos=True):
1345 1344 all_ = []
1346 1345
1347 1346 def _get_members(root_gr):
1348 1347 if include_repos:
1349 1348 for r in root_gr.repositories:
1350 1349 all_.append(r)
1351 1350 childs = root_gr.children.all()
1352 1351 if childs:
1353 1352 for gr in childs:
1354 1353 all_.append(gr)
1355 1354 _get_members(gr)
1356 1355
1357 1356 _get_members(self)
1358 1357 return [self] + all_
1359 1358
1360 1359 def recursive_groups_and_repos(self):
1361 1360 """
1362 1361 Recursive return all groups, with repositories in those groups
1363 1362 """
1364 1363 return self._recursive_objects()
1365 1364
1366 1365 def recursive_groups(self):
1367 1366 """
1368 1367 Returns all children groups for this group including children of children
1369 1368 """
1370 1369 return self._recursive_objects(include_repos=False)
1371 1370
1372 1371 def get_new_name(self, group_name):
1373 1372 """
1374 1373 returns new full group name based on parent and new name
1375 1374
1376 1375 :param group_name:
1377 1376 """
1378 1377 path_prefix = (self.parent_group.full_path_splitted if
1379 1378 self.parent_group else [])
1380 1379 return RepoGroup.url_sep().join(path_prefix + [group_name])
1381 1380
1382 1381
1383 1382 class Permission(Base, BaseModel):
1384 1383 __tablename__ = 'permissions'
1385 1384 __table_args__ = (
1386 1385 Index('p_perm_name_idx', 'permission_name'),
1387 1386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1388 1387 'mysql_charset': 'utf8'},
1389 1388 )
1390 1389 PERMS = [
1391 1390 ('repository.none', _('Repository no access')),
1392 1391 ('repository.read', _('Repository read access')),
1393 1392 ('repository.write', _('Repository write access')),
1394 1393 ('repository.admin', _('Repository admin access')),
1395 1394
1396 1395 ('group.none', _('Repository group no access')),
1397 1396 ('group.read', _('Repository group read access')),
1398 1397 ('group.write', _('Repository group write access')),
1399 1398 ('group.admin', _('Repository group admin access')),
1400 1399
1401 1400 ('hg.admin', _('RhodeCode Administrator')),
1402 1401 ('hg.create.none', _('Repository creation disabled')),
1403 1402 ('hg.create.repository', _('Repository creation enabled')),
1404 1403 ('hg.fork.none', _('Repository forking disabled')),
1405 1404 ('hg.fork.repository', _('Repository forking enabled')),
1406 1405 ('hg.register.none', _('Register disabled')),
1407 1406 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1408 1407 'with manual activation')),
1409 1408
1410 1409 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1411 1410 'with auto activation')),
1412 1411 ]
1413 1412
1414 1413 # defines which permissions are more important higher the more important
1415 1414 PERM_WEIGHTS = {
1416 1415 'repository.none': 0,
1417 1416 'repository.read': 1,
1418 1417 'repository.write': 3,
1419 1418 'repository.admin': 4,
1420 1419
1421 1420 'group.none': 0,
1422 1421 'group.read': 1,
1423 1422 'group.write': 3,
1424 1423 'group.admin': 4,
1425 1424
1426 1425 'hg.fork.none': 0,
1427 1426 'hg.fork.repository': 1,
1428 1427 'hg.create.none': 0,
1429 1428 'hg.create.repository':1
1430 1429 }
1431 1430
1432 1431 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1433 1432 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1434 1433 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1435 1434
1436 1435 def __unicode__(self):
1437 1436 return u"<%s('%s:%s')>" % (
1438 1437 self.__class__.__name__, self.permission_id, self.permission_name
1439 1438 )
1440 1439
1441 1440 @classmethod
1442 1441 def get_by_key(cls, key):
1443 1442 return cls.query().filter(cls.permission_name == key).scalar()
1444 1443
1445 1444 @classmethod
1446 1445 def get_default_perms(cls, default_user_id):
1447 1446 q = Session().query(UserRepoToPerm, Repository, cls)\
1448 1447 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1449 1448 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1450 1449 .filter(UserRepoToPerm.user_id == default_user_id)
1451 1450
1452 1451 return q.all()
1453 1452
1454 1453 @classmethod
1455 1454 def get_default_group_perms(cls, default_user_id):
1456 1455 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1457 1456 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1458 1457 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1459 1458 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1460 1459
1461 1460 return q.all()
1462 1461
1463 1462
1464 1463 class UserRepoToPerm(Base, BaseModel):
1465 1464 __tablename__ = 'repo_to_perm'
1466 1465 __table_args__ = (
1467 1466 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1468 1467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1469 1468 'mysql_charset': 'utf8'}
1470 1469 )
1471 1470 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1472 1471 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1473 1472 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1474 1473 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1475 1474
1476 1475 user = relationship('User')
1477 1476 repository = relationship('Repository')
1478 1477 permission = relationship('Permission')
1479 1478
1480 1479 @classmethod
1481 1480 def create(cls, user, repository, permission):
1482 1481 n = cls()
1483 1482 n.user = user
1484 1483 n.repository = repository
1485 1484 n.permission = permission
1486 1485 Session().add(n)
1487 1486 return n
1488 1487
1489 1488 def __unicode__(self):
1490 1489 return u'<user:%s => %s >' % (self.user, self.repository)
1491 1490
1492 1491
1493 1492 class UserToPerm(Base, BaseModel):
1494 1493 __tablename__ = 'user_to_perm'
1495 1494 __table_args__ = (
1496 1495 UniqueConstraint('user_id', 'permission_id'),
1497 1496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1498 1497 'mysql_charset': 'utf8'}
1499 1498 )
1500 1499 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1501 1500 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1502 1501 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1503 1502
1504 1503 user = relationship('User')
1505 1504 permission = relationship('Permission', lazy='joined')
1506 1505
1507 1506
1508 1507 class UserGroupRepoToPerm(Base, BaseModel):
1509 1508 __tablename__ = 'users_group_repo_to_perm'
1510 1509 __table_args__ = (
1511 1510 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1512 1511 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1513 1512 'mysql_charset': 'utf8'}
1514 1513 )
1515 1514 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1516 1515 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1517 1516 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1518 1517 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1519 1518
1520 1519 users_group = relationship('UserGroup')
1521 1520 permission = relationship('Permission')
1522 1521 repository = relationship('Repository')
1523 1522
1524 1523 @classmethod
1525 1524 def create(cls, users_group, repository, permission):
1526 1525 n = cls()
1527 1526 n.users_group = users_group
1528 1527 n.repository = repository
1529 1528 n.permission = permission
1530 1529 Session().add(n)
1531 1530 return n
1532 1531
1533 1532 def __unicode__(self):
1534 1533 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1535 1534
1536 1535
1537 1536 class UserGroupToPerm(Base, BaseModel):
1538 1537 __tablename__ = 'users_group_to_perm'
1539 1538 __table_args__ = (
1540 1539 UniqueConstraint('users_group_id', 'permission_id',),
1541 1540 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1542 1541 'mysql_charset': 'utf8'}
1543 1542 )
1544 1543 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1545 1544 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1546 1545 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1547 1546
1548 1547 users_group = relationship('UserGroup')
1549 1548 permission = relationship('Permission')
1550 1549
1551 1550
1552 1551 class UserRepoGroupToPerm(Base, BaseModel):
1553 1552 __tablename__ = 'user_repo_group_to_perm'
1554 1553 __table_args__ = (
1555 1554 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1556 1555 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1557 1556 'mysql_charset': 'utf8'}
1558 1557 )
1559 1558
1560 1559 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1561 1560 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1562 1561 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1563 1562 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1564 1563
1565 1564 user = relationship('User')
1566 1565 group = relationship('RepoGroup')
1567 1566 permission = relationship('Permission')
1568 1567
1569 1568
1570 1569 class UserGroupRepoGroupToPerm(Base, BaseModel):
1571 1570 __tablename__ = 'users_group_repo_group_to_perm'
1572 1571 __table_args__ = (
1573 1572 UniqueConstraint('users_group_id', 'group_id'),
1574 1573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1575 1574 'mysql_charset': 'utf8'}
1576 1575 )
1577 1576
1578 1577 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)
1579 1578 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1580 1579 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1581 1580 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1582 1581
1583 1582 users_group = relationship('UserGroup')
1584 1583 permission = relationship('Permission')
1585 1584 group = relationship('RepoGroup')
1586 1585
1587 1586
1588 1587 class Statistics(Base, BaseModel):
1589 1588 __tablename__ = 'statistics'
1590 1589 __table_args__ = (
1591 1590 UniqueConstraint('repository_id'),
1592 1591 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1593 1592 'mysql_charset': 'utf8'}
1594 1593 )
1595 1594 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1596 1595 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1597 1596 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1598 1597 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1599 1598 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1600 1599 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1601 1600
1602 1601 repository = relationship('Repository', single_parent=True)
1603 1602
1604 1603
1605 1604 class UserFollowing(Base, BaseModel):
1606 1605 __tablename__ = 'user_followings'
1607 1606 __table_args__ = (
1608 1607 UniqueConstraint('user_id', 'follows_repository_id'),
1609 1608 UniqueConstraint('user_id', 'follows_user_id'),
1610 1609 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1611 1610 'mysql_charset': 'utf8'}
1612 1611 )
1613 1612
1614 1613 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1615 1614 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1616 1615 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1617 1616 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1618 1617 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1619 1618
1620 1619 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1621 1620
1622 1621 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1623 1622 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1624 1623
1625 1624 @classmethod
1626 1625 def get_repo_followers(cls, repo_id):
1627 1626 return cls.query().filter(cls.follows_repo_id == repo_id)
1628 1627
1629 1628
1630 1629 class CacheInvalidation(Base, BaseModel):
1631 1630 __tablename__ = 'cache_invalidation'
1632 1631 __table_args__ = (
1633 1632 UniqueConstraint('cache_key'),
1634 1633 Index('key_idx', 'cache_key'),
1635 1634 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1636 1635 'mysql_charset': 'utf8'},
1637 1636 )
1638 1637 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1639 1638 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1640 1639 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1641 1640 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1642 1641
1643 1642 def __init__(self, cache_key, cache_args=''):
1644 1643 self.cache_key = cache_key
1645 1644 self.cache_args = cache_args
1646 1645 self.cache_active = False
1647 1646
1648 1647 def __unicode__(self):
1649 1648 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1650 1649 self.cache_id, self.cache_key)
1651 1650
1652 1651 @property
1653 1652 def prefix(self):
1654 1653 _split = self.cache_key.split(self.cache_args, 1)
1655 1654 if _split and len(_split) == 2:
1656 1655 return _split[0]
1657 1656 return ''
1658 1657
1659 1658 @classmethod
1660 1659 def clear_cache(cls):
1661 1660 cls.query().delete()
1662 1661
1663 1662 @classmethod
1664 1663 def _get_key(cls, key):
1665 1664 """
1666 1665 Wrapper for generating a key, together with a prefix
1667 1666
1668 1667 :param key:
1669 1668 """
1670 1669 import rhodecode
1671 1670 prefix = ''
1672 1671 org_key = key
1673 1672 iid = rhodecode.CONFIG.get('instance_id')
1674 1673 if iid:
1675 1674 prefix = iid
1676 1675
1677 1676 return "%s%s" % (prefix, key), prefix, org_key
1678 1677
1679 1678 @classmethod
1680 1679 def get_by_key(cls, key):
1681 1680 return cls.query().filter(cls.cache_key == key).scalar()
1682 1681
1683 1682 @classmethod
1684 1683 def get_by_repo_name(cls, repo_name):
1685 1684 return cls.query().filter(cls.cache_args == repo_name).all()
1686 1685
1687 1686 @classmethod
1688 1687 def _get_or_create_key(cls, key, repo_name, commit=True):
1689 1688 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1690 1689 if not inv_obj:
1691 1690 try:
1692 1691 inv_obj = CacheInvalidation(key, repo_name)
1693 1692 Session().add(inv_obj)
1694 1693 if commit:
1695 1694 Session().commit()
1696 1695 except Exception:
1697 1696 log.error(traceback.format_exc())
1698 1697 Session().rollback()
1699 1698 return inv_obj
1700 1699
1701 1700 @classmethod
1702 1701 def invalidate(cls, key):
1703 1702 """
1704 1703 Returns Invalidation object if this given key should be invalidated
1705 1704 None otherwise. `cache_active = False` means that this cache
1706 1705 state is not valid and needs to be invalidated
1707 1706
1708 1707 :param key:
1709 1708 """
1710 1709 repo_name = key
1711 1710 repo_name = remove_suffix(repo_name, '_README')
1712 1711 repo_name = remove_suffix(repo_name, '_RSS')
1713 1712 repo_name = remove_suffix(repo_name, '_ATOM')
1714 1713
1715 1714 # adds instance prefix
1716 1715 key, _prefix, _org_key = cls._get_key(key)
1717 1716 inv = cls._get_or_create_key(key, repo_name)
1718 1717
1719 1718 if inv and inv.cache_active is False:
1720 1719 return inv
1721 1720
1722 1721 @classmethod
1723 1722 def set_invalidate(cls, key=None, repo_name=None):
1724 1723 """
1725 1724 Mark this Cache key for invalidation, either by key or whole
1726 1725 cache sets based on repo_name
1727 1726
1728 1727 :param key:
1729 1728 """
1730 1729 invalidated_keys = []
1731 1730 if key:
1732 1731 key, _prefix, _org_key = cls._get_key(key)
1733 1732 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1734 1733 elif repo_name:
1735 1734 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1736 1735
1737 1736 try:
1738 1737 for inv_obj in inv_objs:
1739 1738 inv_obj.cache_active = False
1740 1739 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1741 1740 % (inv_obj, key, safe_str(repo_name)))
1742 1741 invalidated_keys.append(inv_obj.cache_key)
1743 1742 Session().add(inv_obj)
1744 1743 Session().commit()
1745 1744 except Exception:
1746 1745 log.error(traceback.format_exc())
1747 1746 Session().rollback()
1748 1747 return invalidated_keys
1749 1748
1750 1749 @classmethod
1751 1750 def set_valid(cls, key):
1752 1751 """
1753 1752 Mark this cache key as active and currently cached
1754 1753
1755 1754 :param key:
1756 1755 """
1757 1756 inv_obj = cls.get_by_key(key)
1758 1757 inv_obj.cache_active = True
1759 1758 Session().add(inv_obj)
1760 1759 Session().commit()
1761 1760
1762 1761 @classmethod
1763 1762 def get_cache_map(cls):
1764 1763
1765 1764 class cachemapdict(dict):
1766 1765
1767 1766 def __init__(self, *args, **kwargs):
1768 1767 fixkey = kwargs.get('fixkey')
1769 1768 if fixkey:
1770 1769 del kwargs['fixkey']
1771 1770 self.fixkey = fixkey
1772 1771 super(cachemapdict, self).__init__(*args, **kwargs)
1773 1772
1774 1773 def __getattr__(self, name):
1775 1774 key = name
1776 1775 if self.fixkey:
1777 1776 key, _prefix, _org_key = cls._get_key(key)
1778 1777 if key in self.__dict__:
1779 1778 return self.__dict__[key]
1780 1779 else:
1781 1780 return self[key]
1782 1781
1783 1782 def __getitem__(self, key):
1784 1783 if self.fixkey:
1785 1784 key, _prefix, _org_key = cls._get_key(key)
1786 1785 try:
1787 1786 return super(cachemapdict, self).__getitem__(key)
1788 1787 except KeyError:
1789 1788 return
1790 1789
1791 1790 cache_map = cachemapdict(fixkey=True)
1792 1791 for obj in cls.query().all():
1793 1792 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1794 1793 return cache_map
1795 1794
1796 1795
1797 1796 class ChangesetComment(Base, BaseModel):
1798 1797 __tablename__ = 'changeset_comments'
1799 1798 __table_args__ = (
1800 1799 Index('cc_revision_idx', 'revision'),
1801 1800 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1802 1801 'mysql_charset': 'utf8'},
1803 1802 )
1804 1803 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1805 1804 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1806 1805 revision = Column('revision', String(40), nullable=True)
1807 1806 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1808 1807 line_no = Column('line_no', Unicode(10), nullable=True)
1809 1808 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1810 1809 f_path = Column('f_path', Unicode(1000), nullable=True)
1811 1810 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1812 1811 text = Column('text', UnicodeText(25000), nullable=False)
1813 1812 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1814 1813 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1815 1814
1816 1815 author = relationship('User', lazy='joined')
1817 1816 repo = relationship('Repository')
1818 1817 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1819 1818 pull_request = relationship('PullRequest', lazy='joined')
1820 1819
1821 1820 @classmethod
1822 1821 def get_users(cls, revision=None, pull_request_id=None):
1823 1822 """
1824 1823 Returns user associated with this ChangesetComment. ie those
1825 1824 who actually commented
1826 1825
1827 1826 :param cls:
1828 1827 :param revision:
1829 1828 """
1830 1829 q = Session().query(User)\
1831 1830 .join(ChangesetComment.author)
1832 1831 if revision:
1833 1832 q = q.filter(cls.revision == revision)
1834 1833 elif pull_request_id:
1835 1834 q = q.filter(cls.pull_request_id == pull_request_id)
1836 1835 return q.all()
1837 1836
1838 1837
1839 1838 class ChangesetStatus(Base, BaseModel):
1840 1839 __tablename__ = 'changeset_statuses'
1841 1840 __table_args__ = (
1842 1841 Index('cs_revision_idx', 'revision'),
1843 1842 Index('cs_version_idx', 'version'),
1844 1843 UniqueConstraint('repo_id', 'revision', 'version'),
1845 1844 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1846 1845 'mysql_charset': 'utf8'}
1847 1846 )
1848 1847 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1849 1848 STATUS_APPROVED = 'approved'
1850 1849 STATUS_REJECTED = 'rejected'
1851 1850 STATUS_UNDER_REVIEW = 'under_review'
1852 1851
1853 1852 STATUSES = [
1854 1853 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1855 1854 (STATUS_APPROVED, _("Approved")),
1856 1855 (STATUS_REJECTED, _("Rejected")),
1857 1856 (STATUS_UNDER_REVIEW, _("Under Review")),
1858 1857 ]
1859 1858
1860 1859 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1861 1860 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1862 1861 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1863 1862 revision = Column('revision', String(40), nullable=False)
1864 1863 status = Column('status', String(128), nullable=False, default=DEFAULT)
1865 1864 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1866 1865 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1867 1866 version = Column('version', Integer(), nullable=False, default=0)
1868 1867 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1869 1868
1870 1869 author = relationship('User', lazy='joined')
1871 1870 repo = relationship('Repository')
1872 1871 comment = relationship('ChangesetComment', lazy='joined')
1873 1872 pull_request = relationship('PullRequest', lazy='joined')
1874 1873
1875 1874 def __unicode__(self):
1876 1875 return u"<%s('%s:%s')>" % (
1877 1876 self.__class__.__name__,
1878 1877 self.status, self.author
1879 1878 )
1880 1879
1881 1880 @classmethod
1882 1881 def get_status_lbl(cls, value):
1883 1882 return dict(cls.STATUSES).get(value)
1884 1883
1885 1884 @property
1886 1885 def status_lbl(self):
1887 1886 return ChangesetStatus.get_status_lbl(self.status)
1888 1887
1889 1888
1890 1889 class PullRequest(Base, BaseModel):
1891 1890 __tablename__ = 'pull_requests'
1892 1891 __table_args__ = (
1893 1892 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1894 1893 'mysql_charset': 'utf8'},
1895 1894 )
1896 1895
1897 1896 STATUS_NEW = u'new'
1898 1897 STATUS_OPEN = u'open'
1899 1898 STATUS_CLOSED = u'closed'
1900 1899
1901 1900 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1902 1901 title = Column('title', Unicode(256), nullable=True)
1903 1902 description = Column('description', UnicodeText(10240), nullable=True)
1904 1903 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1905 1904 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1906 1905 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1907 1906 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1908 1907 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1909 1908 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1910 1909 org_ref = Column('org_ref', Unicode(256), nullable=False)
1911 1910 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1912 1911 other_ref = Column('other_ref', Unicode(256), nullable=False)
1913 1912
1914 1913 @hybrid_property
1915 1914 def revisions(self):
1916 1915 return self._revisions.split(':')
1917 1916
1918 1917 @revisions.setter
1919 1918 def revisions(self, val):
1920 1919 self._revisions = ':'.join(val)
1921 1920
1922 1921 @property
1923 1922 def org_ref_parts(self):
1924 1923 return self.org_ref.split(':')
1925 1924
1926 1925 @property
1927 1926 def other_ref_parts(self):
1928 1927 return self.other_ref.split(':')
1929 1928
1930 1929 author = relationship('User', lazy='joined')
1931 1930 reviewers = relationship('PullRequestReviewers',
1932 1931 cascade="all, delete, delete-orphan")
1933 1932 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1934 1933 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1935 1934 statuses = relationship('ChangesetStatus')
1936 1935 comments = relationship('ChangesetComment',
1937 1936 cascade="all, delete, delete-orphan")
1938 1937
1939 1938 def is_closed(self):
1940 1939 return self.status == self.STATUS_CLOSED
1941 1940
1942 1941 @property
1943 1942 def last_review_status(self):
1944 1943 return self.statuses[-1].status if self.statuses else ''
1945 1944
1946 1945 def __json__(self):
1947 1946 return dict(
1948 1947 revisions=self.revisions
1949 1948 )
1950 1949
1951 1950
1952 1951 class PullRequestReviewers(Base, BaseModel):
1953 1952 __tablename__ = 'pull_request_reviewers'
1954 1953 __table_args__ = (
1955 1954 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1956 1955 'mysql_charset': 'utf8'},
1957 1956 )
1958 1957
1959 1958 def __init__(self, user=None, pull_request=None):
1960 1959 self.user = user
1961 1960 self.pull_request = pull_request
1962 1961
1963 1962 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1964 1963 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1965 1964 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1966 1965
1967 1966 user = relationship('User')
1968 1967 pull_request = relationship('PullRequest')
1969 1968
1970 1969
1971 1970 class Notification(Base, BaseModel):
1972 1971 __tablename__ = 'notifications'
1973 1972 __table_args__ = (
1974 1973 Index('notification_type_idx', 'type'),
1975 1974 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1976 1975 'mysql_charset': 'utf8'},
1977 1976 )
1978 1977
1979 1978 TYPE_CHANGESET_COMMENT = u'cs_comment'
1980 1979 TYPE_MESSAGE = u'message'
1981 1980 TYPE_MENTION = u'mention'
1982 1981 TYPE_REGISTRATION = u'registration'
1983 1982 TYPE_PULL_REQUEST = u'pull_request'
1984 1983 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1985 1984
1986 1985 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1987 1986 subject = Column('subject', Unicode(512), nullable=True)
1988 1987 body = Column('body', UnicodeText(50000), nullable=True)
1989 1988 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1990 1989 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1991 1990 type_ = Column('type', Unicode(256))
1992 1991
1993 1992 created_by_user = relationship('User')
1994 1993 notifications_to_users = relationship('UserNotification', lazy='joined',
1995 1994 cascade="all, delete, delete-orphan")
1996 1995
1997 1996 @property
1998 1997 def recipients(self):
1999 1998 return [x.user for x in UserNotification.query()\
2000 1999 .filter(UserNotification.notification == self)\
2001 2000 .order_by(UserNotification.user_id.asc()).all()]
2002 2001
2003 2002 @classmethod
2004 2003 def create(cls, created_by, subject, body, recipients, type_=None):
2005 2004 if type_ is None:
2006 2005 type_ = Notification.TYPE_MESSAGE
2007 2006
2008 2007 notification = cls()
2009 2008 notification.created_by_user = created_by
2010 2009 notification.subject = subject
2011 2010 notification.body = body
2012 2011 notification.type_ = type_
2013 2012 notification.created_on = datetime.datetime.now()
2014 2013
2015 2014 for u in recipients:
2016 2015 assoc = UserNotification()
2017 2016 assoc.notification = notification
2018 2017 u.notifications.append(assoc)
2019 2018 Session().add(notification)
2020 2019 return notification
2021 2020
2022 2021 @property
2023 2022 def description(self):
2024 2023 from rhodecode.model.notification import NotificationModel
2025 2024 return NotificationModel().make_description(self)
2026 2025
2027 2026
2028 2027 class UserNotification(Base, BaseModel):
2029 2028 __tablename__ = 'user_to_notification'
2030 2029 __table_args__ = (
2031 2030 UniqueConstraint('user_id', 'notification_id'),
2032 2031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2033 2032 'mysql_charset': 'utf8'}
2034 2033 )
2035 2034 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2036 2035 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2037 2036 read = Column('read', Boolean, default=False)
2038 2037 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2039 2038
2040 2039 user = relationship('User', lazy="joined")
2041 2040 notification = relationship('Notification', lazy="joined",
2042 2041 order_by=lambda: Notification.created_on.desc(),)
2043 2042
2044 2043 def mark_as_read(self):
2045 2044 self.read = True
2046 2045 Session().add(self)
2047 2046
2048 2047
2049 2048 class DbMigrateVersion(Base, BaseModel):
2050 2049 __tablename__ = 'db_migrate_version'
2051 2050 __table_args__ = (
2052 2051 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2053 2052 'mysql_charset': 'utf8'},
2054 2053 )
2055 2054 repository_id = Column('repository_id', String(250), primary_key=True)
2056 2055 repository_path = Column('repository_path', Text)
2057 2056 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now