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