##// END OF EJS Templates
fixed issues with importing non-ascii repo names
marcink -
r3636:ddc4c12f beta
parent child Browse files
Show More
@@ -1,2039 +1,2041 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, _set_extras
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 Exception:
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, self.repo_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 return os.path.join(*p)
910 return os.path.join(*map(safe_unicode, 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 is_valid(cls, repo_name):
941 941 """
942 942 returns True if given repo name is a valid filesystem repository
943 943
944 944 :param cls:
945 945 :param repo_name:
946 946 """
947 947 from rhodecode.lib.utils import is_valid_repo
948 948
949 949 return is_valid_repo(repo_name, cls.base_path())
950 950
951 951 def get_api_data(self):
952 952 """
953 953 Common function for generating repo api data
954 954
955 955 """
956 956 repo = self
957 957 data = dict(
958 958 repo_id=repo.repo_id,
959 959 repo_name=repo.repo_name,
960 960 repo_type=repo.repo_type,
961 961 clone_uri=repo.clone_uri,
962 962 private=repo.private,
963 963 created_on=repo.created_on,
964 964 description=repo.description,
965 965 landing_rev=repo.landing_rev,
966 966 owner=repo.user.username,
967 967 fork_of=repo.fork.repo_name if repo.fork else None,
968 968 enable_statistics=repo.enable_statistics,
969 969 enable_locking=repo.enable_locking,
970 970 enable_downloads=repo.enable_downloads,
971 971 last_changeset=repo.changeset_cache,
972 972 locked_by=User.get(self.locked[0]).get_api_data() \
973 973 if self.locked[0] else None,
974 974 locked_date=time_to_datetime(self.locked[1]) \
975 975 if self.locked[1] else None
976 976 )
977 977 rc_config = RhodeCodeSetting.get_app_settings()
978 978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
979 979 if repository_fields:
980 980 for f in self.extra_fields:
981 981 data[f.field_key_prefixed] = f.field_value
982 982
983 983 return data
984 984
985 985 @classmethod
986 986 def lock(cls, repo, user_id):
987 987 repo.locked = [user_id, time.time()]
988 988 Session().add(repo)
989 989 Session().commit()
990 990
991 991 @classmethod
992 992 def unlock(cls, repo):
993 993 repo.locked = None
994 994 Session().add(repo)
995 995 Session().commit()
996 996
997 997 @classmethod
998 998 def getlock(cls, repo):
999 999 return repo.locked
1000 1000
1001 1001 @property
1002 1002 def last_db_change(self):
1003 1003 return self.updated_on
1004 1004
1005 1005 def clone_url(self, **override):
1006 1006 from pylons import url
1007 1007 from urlparse import urlparse
1008 1008 import urllib
1009 1009 parsed_url = urlparse(url('home', qualified=True))
1010 1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1011 1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1012 1012 args = {
1013 1013 'user': '',
1014 1014 'pass': '',
1015 1015 'scheme': parsed_url.scheme,
1016 1016 'netloc': parsed_url.netloc,
1017 1017 'prefix': decoded_path,
1018 1018 'path': self.repo_name
1019 1019 }
1020 1020
1021 1021 args.update(override)
1022 1022 return default_clone_uri % args
1023 1023
1024 1024 #==========================================================================
1025 1025 # SCM PROPERTIES
1026 1026 #==========================================================================
1027 1027
1028 1028 def get_changeset(self, rev=None):
1029 1029 return get_changeset_safe(self.scm_instance, rev)
1030 1030
1031 1031 def get_landing_changeset(self):
1032 1032 """
1033 1033 Returns landing changeset, or if that doesn't exist returns the tip
1034 1034 """
1035 1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1036 1036 return cs
1037 1037
1038 1038 def update_changeset_cache(self, cs_cache=None):
1039 1039 """
1040 1040 Update cache of last changeset for repository, keys should be::
1041 1041
1042 1042 short_id
1043 1043 raw_id
1044 1044 revision
1045 1045 message
1046 1046 date
1047 1047 author
1048 1048
1049 1049 :param cs_cache:
1050 1050 """
1051 1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1052 1052 if cs_cache is None:
1053 1053 cs_cache = EmptyChangeset()
1054 1054 # use no-cache version here
1055 1055 scm_repo = self.scm_instance_no_cache()
1056 1056 if scm_repo:
1057 1057 cs_cache = scm_repo.get_changeset()
1058 1058
1059 1059 if isinstance(cs_cache, BaseChangeset):
1060 1060 cs_cache = cs_cache.__json__()
1061 1061
1062 1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1063 1063 _default = datetime.datetime.fromtimestamp(0)
1064 1064 last_change = cs_cache.get('date') or _default
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1065 log.debug('updated repo %s with new cs cache %s'
1066 % (self.repo_name, cs_cache))
1066 1067 self.updated_on = last_change
1067 1068 self.changeset_cache = cs_cache
1068 1069 Session().add(self)
1069 1070 Session().commit()
1070 1071 else:
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1072 log.debug('Skipping repo:%s already with latest changes'
1073 % self.repo_name)
1072 1074
1073 1075 @property
1074 1076 def tip(self):
1075 1077 return self.get_changeset('tip')
1076 1078
1077 1079 @property
1078 1080 def author(self):
1079 1081 return self.tip.author
1080 1082
1081 1083 @property
1082 1084 def last_change(self):
1083 1085 return self.scm_instance.last_change
1084 1086
1085 1087 def get_comments(self, revisions=None):
1086 1088 """
1087 1089 Returns comments for this repository grouped by revisions
1088 1090
1089 1091 :param revisions: filter query by revisions only
1090 1092 """
1091 1093 cmts = ChangesetComment.query()\
1092 1094 .filter(ChangesetComment.repo == self)
1093 1095 if revisions:
1094 1096 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1095 1097 grouped = defaultdict(list)
1096 1098 for cmt in cmts.all():
1097 1099 grouped[cmt.revision].append(cmt)
1098 1100 return grouped
1099 1101
1100 1102 def statuses(self, revisions=None):
1101 1103 """
1102 1104 Returns statuses for this repository
1103 1105
1104 1106 :param revisions: list of revisions to get statuses for
1105 1107 :type revisions: list
1106 1108 """
1107 1109
1108 1110 statuses = ChangesetStatus.query()\
1109 1111 .filter(ChangesetStatus.repo == self)\
1110 1112 .filter(ChangesetStatus.version == 0)
1111 1113 if revisions:
1112 1114 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1113 1115 grouped = {}
1114 1116
1115 1117 #maybe we have open new pullrequest without a status ?
1116 1118 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1117 1119 status_lbl = ChangesetStatus.get_status_lbl(stat)
1118 1120 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1119 1121 for rev in pr.revisions:
1120 1122 pr_id = pr.pull_request_id
1121 1123 pr_repo = pr.other_repo.repo_name
1122 1124 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1123 1125
1124 1126 for stat in statuses.all():
1125 1127 pr_id = pr_repo = None
1126 1128 if stat.pull_request:
1127 1129 pr_id = stat.pull_request.pull_request_id
1128 1130 pr_repo = stat.pull_request.other_repo.repo_name
1129 1131 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1130 1132 pr_id, pr_repo]
1131 1133 return grouped
1132 1134
1133 1135 def _repo_size(self):
1134 1136 from rhodecode.lib import helpers as h
1135 1137 log.debug('calculating repository size...')
1136 1138 return h.format_byte_size(self.scm_instance.size)
1137 1139
1138 1140 #==========================================================================
1139 1141 # SCM CACHE INSTANCE
1140 1142 #==========================================================================
1141 1143
1142 1144 @property
1143 1145 def invalidate(self):
1144 1146 return CacheInvalidation.invalidate(self.repo_name)
1145 1147
1146 1148 def set_invalidate(self):
1147 1149 """
1148 1150 set a cache for invalidation for this instance
1149 1151 """
1150 1152 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1151 1153
1152 1154 def scm_instance_no_cache(self):
1153 1155 return self.__get_instance()
1154 1156
1155 1157 @LazyProperty
1156 1158 def scm_instance(self):
1157 1159 import rhodecode
1158 1160 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 1161 if full_cache:
1160 1162 return self.scm_instance_cached()
1161 1163 return self.__get_instance()
1162 1164
1163 1165 def scm_instance_cached(self, cache_map=None):
1164 1166 @cache_region('long_term')
1165 1167 def _c(repo_name):
1166 1168 return self.__get_instance()
1167 1169 rn = self.repo_name
1168 1170 log.debug('Getting cached instance of repo')
1169 1171
1170 1172 if cache_map:
1171 1173 # get using prefilled cache_map
1172 1174 invalidate_repo = cache_map[self.repo_name]
1173 1175 if invalidate_repo:
1174 1176 invalidate_repo = (None if invalidate_repo.cache_active
1175 1177 else invalidate_repo)
1176 1178 else:
1177 1179 # get from invalidate
1178 1180 invalidate_repo = self.invalidate
1179 1181
1180 1182 if invalidate_repo is not None:
1181 1183 region_invalidate(_c, None, rn)
1182 1184 # update our cache
1183 1185 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 1186 return _c(rn)
1185 1187
1186 1188 def __get_instance(self):
1187 1189 repo_full_path = self.repo_full_path
1188 1190 try:
1189 1191 alias = get_scm(repo_full_path)[0]
1190 1192 log.debug('Creating instance of %s repository from %s'
1191 1193 % (alias, repo_full_path))
1192 1194 backend = get_backend(alias)
1193 1195 except VCSError:
1194 1196 log.error(traceback.format_exc())
1195 1197 log.error('Perhaps this repository is in db and not in '
1196 1198 'filesystem run rescan repositories with '
1197 1199 '"destroy old data " option from admin panel')
1198 1200 return
1199 1201
1200 1202 if alias == 'hg':
1201 1203
1202 1204 repo = backend(safe_str(repo_full_path), create=False,
1203 1205 baseui=self._ui)
1204 1206 # skip hidden web repository
1205 1207 if repo._get_hidden():
1206 1208 return
1207 1209 else:
1208 1210 repo = backend(repo_full_path, create=False)
1209 1211
1210 1212 return repo
1211 1213
1212 1214
1213 1215 class RepoGroup(Base, BaseModel):
1214 1216 __tablename__ = 'groups'
1215 1217 __table_args__ = (
1216 1218 UniqueConstraint('group_name', 'group_parent_id'),
1217 1219 CheckConstraint('group_id != group_parent_id'),
1218 1220 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 1221 'mysql_charset': 'utf8'},
1220 1222 )
1221 1223 __mapper_args__ = {'order_by': 'group_name'}
1222 1224
1223 1225 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 1226 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1225 1227 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1226 1228 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1227 1229 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1228 1230
1229 1231 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1230 1232 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1231 1233
1232 1234 parent_group = relationship('RepoGroup', remote_side=group_id)
1233 1235
1234 1236 def __init__(self, group_name='', parent_group=None):
1235 1237 self.group_name = group_name
1236 1238 self.parent_group = parent_group
1237 1239
1238 1240 def __unicode__(self):
1239 1241 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1240 1242 self.group_name)
1241 1243
1242 1244 @classmethod
1243 1245 def groups_choices(cls, groups=None, show_empty_group=True):
1244 1246 from webhelpers.html import literal as _literal
1245 1247 if not groups:
1246 1248 groups = cls.query().all()
1247 1249
1248 1250 repo_groups = []
1249 1251 if show_empty_group:
1250 1252 repo_groups = [('-1', '-- %s --' % _('top level'))]
1251 1253 sep = ' &raquo; '
1252 1254 _name = lambda k: _literal(sep.join(k))
1253 1255
1254 1256 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1255 1257 for x in groups])
1256 1258
1257 1259 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1258 1260 return repo_groups
1259 1261
1260 1262 @classmethod
1261 1263 def url_sep(cls):
1262 1264 return URL_SEP
1263 1265
1264 1266 @classmethod
1265 1267 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1266 1268 if case_insensitive:
1267 1269 gr = cls.query()\
1268 1270 .filter(cls.group_name.ilike(group_name))
1269 1271 else:
1270 1272 gr = cls.query()\
1271 1273 .filter(cls.group_name == group_name)
1272 1274 if cache:
1273 1275 gr = gr.options(FromCache(
1274 1276 "sql_cache_short",
1275 1277 "get_group_%s" % _hash_key(group_name)
1276 1278 )
1277 1279 )
1278 1280 return gr.scalar()
1279 1281
1280 1282 @property
1281 1283 def parents(self):
1282 1284 parents_recursion_limit = 5
1283 1285 groups = []
1284 1286 if self.parent_group is None:
1285 1287 return groups
1286 1288 cur_gr = self.parent_group
1287 1289 groups.insert(0, cur_gr)
1288 1290 cnt = 0
1289 1291 while 1:
1290 1292 cnt += 1
1291 1293 gr = getattr(cur_gr, 'parent_group', None)
1292 1294 cur_gr = cur_gr.parent_group
1293 1295 if gr is None:
1294 1296 break
1295 1297 if cnt == parents_recursion_limit:
1296 1298 # this will prevent accidental infinit loops
1297 1299 log.error('group nested more than %s' %
1298 1300 parents_recursion_limit)
1299 1301 break
1300 1302
1301 1303 groups.insert(0, gr)
1302 1304 return groups
1303 1305
1304 1306 @property
1305 1307 def children(self):
1306 1308 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1307 1309
1308 1310 @property
1309 1311 def name(self):
1310 1312 return self.group_name.split(RepoGroup.url_sep())[-1]
1311 1313
1312 1314 @property
1313 1315 def full_path(self):
1314 1316 return self.group_name
1315 1317
1316 1318 @property
1317 1319 def full_path_splitted(self):
1318 1320 return self.group_name.split(RepoGroup.url_sep())
1319 1321
1320 1322 @property
1321 1323 def repositories(self):
1322 1324 return Repository.query()\
1323 1325 .filter(Repository.group == self)\
1324 1326 .order_by(Repository.repo_name)
1325 1327
1326 1328 @property
1327 1329 def repositories_recursive_count(self):
1328 1330 cnt = self.repositories.count()
1329 1331
1330 1332 def children_count(group):
1331 1333 cnt = 0
1332 1334 for child in group.children:
1333 1335 cnt += child.repositories.count()
1334 1336 cnt += children_count(child)
1335 1337 return cnt
1336 1338
1337 1339 return cnt + children_count(self)
1338 1340
1339 1341 def _recursive_objects(self, include_repos=True):
1340 1342 all_ = []
1341 1343
1342 1344 def _get_members(root_gr):
1343 1345 if include_repos:
1344 1346 for r in root_gr.repositories:
1345 1347 all_.append(r)
1346 1348 childs = root_gr.children.all()
1347 1349 if childs:
1348 1350 for gr in childs:
1349 1351 all_.append(gr)
1350 1352 _get_members(gr)
1351 1353
1352 1354 _get_members(self)
1353 1355 return [self] + all_
1354 1356
1355 1357 def recursive_groups_and_repos(self):
1356 1358 """
1357 1359 Recursive return all groups, with repositories in those groups
1358 1360 """
1359 1361 return self._recursive_objects()
1360 1362
1361 1363 def recursive_groups(self):
1362 1364 """
1363 1365 Returns all children groups for this group including children of children
1364 1366 """
1365 1367 return self._recursive_objects(include_repos=False)
1366 1368
1367 1369 def get_new_name(self, group_name):
1368 1370 """
1369 1371 returns new full group name based on parent and new name
1370 1372
1371 1373 :param group_name:
1372 1374 """
1373 1375 path_prefix = (self.parent_group.full_path_splitted if
1374 1376 self.parent_group else [])
1375 1377 return RepoGroup.url_sep().join(path_prefix + [group_name])
1376 1378
1377 1379
1378 1380 class Permission(Base, BaseModel):
1379 1381 __tablename__ = 'permissions'
1380 1382 __table_args__ = (
1381 1383 Index('p_perm_name_idx', 'permission_name'),
1382 1384 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 1385 'mysql_charset': 'utf8'},
1384 1386 )
1385 1387 PERMS = [
1386 1388 ('repository.none', _('Repository no access')),
1387 1389 ('repository.read', _('Repository read access')),
1388 1390 ('repository.write', _('Repository write access')),
1389 1391 ('repository.admin', _('Repository admin access')),
1390 1392
1391 1393 ('group.none', _('Repository group no access')),
1392 1394 ('group.read', _('Repository group read access')),
1393 1395 ('group.write', _('Repository group write access')),
1394 1396 ('group.admin', _('Repository group admin access')),
1395 1397
1396 1398 ('hg.admin', _('RhodeCode Administrator')),
1397 1399 ('hg.create.none', _('Repository creation disabled')),
1398 1400 ('hg.create.repository', _('Repository creation enabled')),
1399 1401 ('hg.fork.none', _('Repository forking disabled')),
1400 1402 ('hg.fork.repository', _('Repository forking enabled')),
1401 1403 ('hg.register.none', _('Register disabled')),
1402 1404 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1403 1405 'with manual activation')),
1404 1406
1405 1407 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1406 1408 'with auto activation')),
1407 1409 ]
1408 1410
1409 1411 # defines which permissions are more important higher the more important
1410 1412 PERM_WEIGHTS = {
1411 1413 'repository.none': 0,
1412 1414 'repository.read': 1,
1413 1415 'repository.write': 3,
1414 1416 'repository.admin': 4,
1415 1417
1416 1418 'group.none': 0,
1417 1419 'group.read': 1,
1418 1420 'group.write': 3,
1419 1421 'group.admin': 4,
1420 1422
1421 1423 'hg.fork.none': 0,
1422 1424 'hg.fork.repository': 1,
1423 1425 'hg.create.none': 0,
1424 1426 'hg.create.repository':1
1425 1427 }
1426 1428
1427 1429 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 1430 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 1431 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430 1432
1431 1433 def __unicode__(self):
1432 1434 return u"<%s('%s:%s')>" % (
1433 1435 self.__class__.__name__, self.permission_id, self.permission_name
1434 1436 )
1435 1437
1436 1438 @classmethod
1437 1439 def get_by_key(cls, key):
1438 1440 return cls.query().filter(cls.permission_name == key).scalar()
1439 1441
1440 1442 @classmethod
1441 1443 def get_default_perms(cls, default_user_id):
1442 1444 q = Session().query(UserRepoToPerm, Repository, cls)\
1443 1445 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1444 1446 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1445 1447 .filter(UserRepoToPerm.user_id == default_user_id)
1446 1448
1447 1449 return q.all()
1448 1450
1449 1451 @classmethod
1450 1452 def get_default_group_perms(cls, default_user_id):
1451 1453 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1452 1454 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1453 1455 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1454 1456 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1455 1457
1456 1458 return q.all()
1457 1459
1458 1460
1459 1461 class UserRepoToPerm(Base, BaseModel):
1460 1462 __tablename__ = 'repo_to_perm'
1461 1463 __table_args__ = (
1462 1464 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1463 1465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 1466 'mysql_charset': 'utf8'}
1465 1467 )
1466 1468 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 1469 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1468 1470 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 1471 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1470 1472
1471 1473 user = relationship('User')
1472 1474 repository = relationship('Repository')
1473 1475 permission = relationship('Permission')
1474 1476
1475 1477 @classmethod
1476 1478 def create(cls, user, repository, permission):
1477 1479 n = cls()
1478 1480 n.user = user
1479 1481 n.repository = repository
1480 1482 n.permission = permission
1481 1483 Session().add(n)
1482 1484 return n
1483 1485
1484 1486 def __unicode__(self):
1485 1487 return u'<user:%s => %s >' % (self.user, self.repository)
1486 1488
1487 1489
1488 1490 class UserToPerm(Base, BaseModel):
1489 1491 __tablename__ = 'user_to_perm'
1490 1492 __table_args__ = (
1491 1493 UniqueConstraint('user_id', 'permission_id'),
1492 1494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 1495 'mysql_charset': 'utf8'}
1494 1496 )
1495 1497 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 1498 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 1499 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1498 1500
1499 1501 user = relationship('User')
1500 1502 permission = relationship('Permission', lazy='joined')
1501 1503
1502 1504
1503 1505 class UserGroupRepoToPerm(Base, BaseModel):
1504 1506 __tablename__ = 'users_group_repo_to_perm'
1505 1507 __table_args__ = (
1506 1508 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1507 1509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 1510 'mysql_charset': 'utf8'}
1509 1511 )
1510 1512 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1511 1513 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1512 1514 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1513 1515 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514 1516
1515 1517 users_group = relationship('UserGroup')
1516 1518 permission = relationship('Permission')
1517 1519 repository = relationship('Repository')
1518 1520
1519 1521 @classmethod
1520 1522 def create(cls, users_group, repository, permission):
1521 1523 n = cls()
1522 1524 n.users_group = users_group
1523 1525 n.repository = repository
1524 1526 n.permission = permission
1525 1527 Session().add(n)
1526 1528 return n
1527 1529
1528 1530 def __unicode__(self):
1529 1531 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1530 1532
1531 1533
1532 1534 class UserGroupToPerm(Base, BaseModel):
1533 1535 __tablename__ = 'users_group_to_perm'
1534 1536 __table_args__ = (
1535 1537 UniqueConstraint('users_group_id', 'permission_id',),
1536 1538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 1539 'mysql_charset': 'utf8'}
1538 1540 )
1539 1541 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 1542 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1541 1543 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1542 1544
1543 1545 users_group = relationship('UserGroup')
1544 1546 permission = relationship('Permission')
1545 1547
1546 1548
1547 1549 class UserRepoGroupToPerm(Base, BaseModel):
1548 1550 __tablename__ = 'user_repo_group_to_perm'
1549 1551 __table_args__ = (
1550 1552 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1551 1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 1554 'mysql_charset': 'utf8'}
1553 1555 )
1554 1556
1555 1557 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1557 1559 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1558 1560 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559 1561
1560 1562 user = relationship('User')
1561 1563 group = relationship('RepoGroup')
1562 1564 permission = relationship('Permission')
1563 1565
1564 1566
1565 1567 class UserGroupRepoGroupToPerm(Base, BaseModel):
1566 1568 __tablename__ = 'users_group_repo_group_to_perm'
1567 1569 __table_args__ = (
1568 1570 UniqueConstraint('users_group_id', 'group_id'),
1569 1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 1572 'mysql_charset': 'utf8'}
1571 1573 )
1572 1574
1573 1575 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)
1574 1576 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 1577 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1576 1578 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1577 1579
1578 1580 users_group = relationship('UserGroup')
1579 1581 permission = relationship('Permission')
1580 1582 group = relationship('RepoGroup')
1581 1583
1582 1584
1583 1585 class Statistics(Base, BaseModel):
1584 1586 __tablename__ = 'statistics'
1585 1587 __table_args__ = (
1586 1588 UniqueConstraint('repository_id'),
1587 1589 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 1590 'mysql_charset': 'utf8'}
1589 1591 )
1590 1592 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 1593 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1592 1594 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1593 1595 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1594 1596 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1595 1597 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1596 1598
1597 1599 repository = relationship('Repository', single_parent=True)
1598 1600
1599 1601
1600 1602 class UserFollowing(Base, BaseModel):
1601 1603 __tablename__ = 'user_followings'
1602 1604 __table_args__ = (
1603 1605 UniqueConstraint('user_id', 'follows_repository_id'),
1604 1606 UniqueConstraint('user_id', 'follows_user_id'),
1605 1607 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 1608 'mysql_charset': 'utf8'}
1607 1609 )
1608 1610
1609 1611 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 1612 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1611 1613 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1612 1614 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1613 1615 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1614 1616
1615 1617 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1616 1618
1617 1619 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1618 1620 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1619 1621
1620 1622 @classmethod
1621 1623 def get_repo_followers(cls, repo_id):
1622 1624 return cls.query().filter(cls.follows_repo_id == repo_id)
1623 1625
1624 1626
1625 1627 class CacheInvalidation(Base, BaseModel):
1626 1628 __tablename__ = 'cache_invalidation'
1627 1629 __table_args__ = (
1628 1630 UniqueConstraint('cache_key'),
1629 1631 Index('key_idx', 'cache_key'),
1630 1632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 1633 'mysql_charset': 'utf8'},
1632 1634 )
1633 1635 # cache_id, not used
1634 1636 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 1637 # cache_key as created by _get_cache_key
1636 1638 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1637 1639 # cache_args is usually a repo_name, possibly with _README/_RSS/_ATOM suffix
1638 1640 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1639 1641 # instance sets cache_active True when it is caching, other instances set cache_active to False to invalidate
1640 1642 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1641 1643
1642 1644 def __init__(self, cache_key, cache_args=''):
1643 1645 self.cache_key = cache_key
1644 1646 self.cache_args = cache_args
1645 1647 self.cache_active = False
1646 1648
1647 1649 def __unicode__(self):
1648 1650 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1649 1651 self.cache_id, self.cache_key)
1650 1652
1651 1653 def get_prefix(self):
1652 1654 """
1653 1655 Guess prefix that might have been used in _get_cache_key to generate self.cache_key .
1654 1656 Only used for informational purposes in repo_edit.html .
1655 1657 """
1656 1658 _split = self.cache_key.split(self.cache_args, 1)
1657 1659 if len(_split) == 2:
1658 1660 return _split[0]
1659 1661 return ''
1660 1662
1661 1663 @classmethod
1662 1664 def _get_cache_key(cls, key):
1663 1665 """
1664 1666 Wrapper for generating a unique cache key for this instance and "key".
1665 1667 """
1666 1668 import rhodecode
1667 1669 prefix = rhodecode.CONFIG.get('instance_id', '')
1668 1670 return "%s%s" % (prefix, key)
1669 1671
1670 1672 @classmethod
1671 1673 def _get_or_create_inv_obj(cls, key, repo_name, commit=True):
1672 1674 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1673 1675 if not inv_obj:
1674 1676 try:
1675 1677 inv_obj = CacheInvalidation(key, repo_name)
1676 1678 Session().add(inv_obj)
1677 1679 if commit:
1678 1680 Session().commit()
1679 1681 except Exception:
1680 1682 log.error(traceback.format_exc())
1681 1683 Session().rollback()
1682 1684 return inv_obj
1683 1685
1684 1686 @classmethod
1685 1687 def invalidate(cls, key):
1686 1688 """
1687 1689 Returns Invalidation object if this given key should be invalidated
1688 1690 None otherwise. `cache_active = False` means that this cache
1689 1691 state is not valid and needs to be invalidated
1690 1692
1691 1693 :param key:
1692 1694 """
1693 1695 repo_name = key
1694 1696 repo_name = remove_suffix(repo_name, '_README')
1695 1697 repo_name = remove_suffix(repo_name, '_RSS')
1696 1698 repo_name = remove_suffix(repo_name, '_ATOM')
1697 1699
1698 1700 cache_key = cls._get_cache_key(key)
1699 1701 inv = cls._get_or_create_inv_obj(cache_key, repo_name)
1700 1702
1701 1703 if inv and not inv.cache_active:
1702 1704 return inv
1703 1705
1704 1706 @classmethod
1705 1707 def set_invalidate(cls, key=None, repo_name=None):
1706 1708 """
1707 1709 Mark this Cache key for invalidation, either by key or whole
1708 1710 cache sets based on repo_name
1709 1711
1710 1712 :param key:
1711 1713 """
1712 1714 invalidated_keys = []
1713 1715 if key:
1714 1716 assert not repo_name
1715 1717 cache_key = cls._get_cache_key(key)
1716 1718 inv_objs = Session().query(cls).filter(cls.cache_key == cache_key).all()
1717 1719 else:
1718 1720 assert repo_name
1719 1721 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1720 1722
1721 1723 try:
1722 1724 for inv_obj in inv_objs:
1723 1725 inv_obj.cache_active = False
1724 1726 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1725 1727 % (inv_obj, key, safe_str(repo_name)))
1726 1728 invalidated_keys.append(inv_obj.cache_key)
1727 1729 Session().add(inv_obj)
1728 1730 Session().commit()
1729 1731 except Exception:
1730 1732 log.error(traceback.format_exc())
1731 1733 Session().rollback()
1732 1734 return invalidated_keys
1733 1735
1734 1736 @classmethod
1735 1737 def set_valid(cls, key):
1736 1738 """
1737 1739 Mark this cache key as active and currently cached
1738 1740
1739 1741 :param key:
1740 1742 """
1741 1743 inv_obj = cls.query().filter(cls.cache_key == key).scalar()
1742 1744 inv_obj.cache_active = True
1743 1745 Session().add(inv_obj)
1744 1746 Session().commit()
1745 1747
1746 1748 @classmethod
1747 1749 def get_cache_map(cls):
1748 1750
1749 1751 class cachemapdict(dict):
1750 1752
1751 1753 def __init__(self, *args, **kwargs):
1752 1754 self.fixkey = kwargs.pop('fixkey', False)
1753 1755 super(cachemapdict, self).__init__(*args, **kwargs)
1754 1756
1755 1757 def __getattr__(self, name):
1756 1758 cache_key = name
1757 1759 if self.fixkey:
1758 1760 cache_key = cls._get_cache_key(name)
1759 1761 if cache_key in self.__dict__:
1760 1762 return self.__dict__[cache_key]
1761 1763 else:
1762 1764 return self[cache_key]
1763 1765
1764 1766 def __getitem__(self, name):
1765 1767 cache_key = name
1766 1768 if self.fixkey:
1767 1769 cache_key = cls._get_cache_key(name)
1768 1770 try:
1769 1771 return super(cachemapdict, self).__getitem__(cache_key)
1770 1772 except KeyError:
1771 1773 return None
1772 1774
1773 1775 cache_map = cachemapdict(fixkey=True)
1774 1776 for obj in cls.query().all():
1775 1777 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1776 1778 return cache_map
1777 1779
1778 1780
1779 1781 class ChangesetComment(Base, BaseModel):
1780 1782 __tablename__ = 'changeset_comments'
1781 1783 __table_args__ = (
1782 1784 Index('cc_revision_idx', 'revision'),
1783 1785 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1784 1786 'mysql_charset': 'utf8'},
1785 1787 )
1786 1788 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1787 1789 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1788 1790 revision = Column('revision', String(40), nullable=True)
1789 1791 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1790 1792 line_no = Column('line_no', Unicode(10), nullable=True)
1791 1793 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1792 1794 f_path = Column('f_path', Unicode(1000), nullable=True)
1793 1795 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1794 1796 text = Column('text', UnicodeText(25000), nullable=False)
1795 1797 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1796 1798 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1797 1799
1798 1800 author = relationship('User', lazy='joined')
1799 1801 repo = relationship('Repository')
1800 1802 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1801 1803 pull_request = relationship('PullRequest', lazy='joined')
1802 1804
1803 1805 @classmethod
1804 1806 def get_users(cls, revision=None, pull_request_id=None):
1805 1807 """
1806 1808 Returns user associated with this ChangesetComment. ie those
1807 1809 who actually commented
1808 1810
1809 1811 :param cls:
1810 1812 :param revision:
1811 1813 """
1812 1814 q = Session().query(User)\
1813 1815 .join(ChangesetComment.author)
1814 1816 if revision:
1815 1817 q = q.filter(cls.revision == revision)
1816 1818 elif pull_request_id:
1817 1819 q = q.filter(cls.pull_request_id == pull_request_id)
1818 1820 return q.all()
1819 1821
1820 1822
1821 1823 class ChangesetStatus(Base, BaseModel):
1822 1824 __tablename__ = 'changeset_statuses'
1823 1825 __table_args__ = (
1824 1826 Index('cs_revision_idx', 'revision'),
1825 1827 Index('cs_version_idx', 'version'),
1826 1828 UniqueConstraint('repo_id', 'revision', 'version'),
1827 1829 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1828 1830 'mysql_charset': 'utf8'}
1829 1831 )
1830 1832 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1831 1833 STATUS_APPROVED = 'approved'
1832 1834 STATUS_REJECTED = 'rejected'
1833 1835 STATUS_UNDER_REVIEW = 'under_review'
1834 1836
1835 1837 STATUSES = [
1836 1838 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1837 1839 (STATUS_APPROVED, _("Approved")),
1838 1840 (STATUS_REJECTED, _("Rejected")),
1839 1841 (STATUS_UNDER_REVIEW, _("Under Review")),
1840 1842 ]
1841 1843
1842 1844 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1843 1845 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1844 1846 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1845 1847 revision = Column('revision', String(40), nullable=False)
1846 1848 status = Column('status', String(128), nullable=False, default=DEFAULT)
1847 1849 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1848 1850 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1849 1851 version = Column('version', Integer(), nullable=False, default=0)
1850 1852 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1851 1853
1852 1854 author = relationship('User', lazy='joined')
1853 1855 repo = relationship('Repository')
1854 1856 comment = relationship('ChangesetComment', lazy='joined')
1855 1857 pull_request = relationship('PullRequest', lazy='joined')
1856 1858
1857 1859 def __unicode__(self):
1858 1860 return u"<%s('%s:%s')>" % (
1859 1861 self.__class__.__name__,
1860 1862 self.status, self.author
1861 1863 )
1862 1864
1863 1865 @classmethod
1864 1866 def get_status_lbl(cls, value):
1865 1867 return dict(cls.STATUSES).get(value)
1866 1868
1867 1869 @property
1868 1870 def status_lbl(self):
1869 1871 return ChangesetStatus.get_status_lbl(self.status)
1870 1872
1871 1873
1872 1874 class PullRequest(Base, BaseModel):
1873 1875 __tablename__ = 'pull_requests'
1874 1876 __table_args__ = (
1875 1877 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1876 1878 'mysql_charset': 'utf8'},
1877 1879 )
1878 1880
1879 1881 STATUS_NEW = u'new'
1880 1882 STATUS_OPEN = u'open'
1881 1883 STATUS_CLOSED = u'closed'
1882 1884
1883 1885 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1884 1886 title = Column('title', Unicode(256), nullable=True)
1885 1887 description = Column('description', UnicodeText(10240), nullable=True)
1886 1888 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1887 1889 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1888 1890 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1889 1891 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1890 1892 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1891 1893 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1892 1894 org_ref = Column('org_ref', Unicode(256), nullable=False)
1893 1895 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1894 1896 other_ref = Column('other_ref', Unicode(256), nullable=False)
1895 1897
1896 1898 @hybrid_property
1897 1899 def revisions(self):
1898 1900 return self._revisions.split(':')
1899 1901
1900 1902 @revisions.setter
1901 1903 def revisions(self, val):
1902 1904 self._revisions = ':'.join(val)
1903 1905
1904 1906 @property
1905 1907 def org_ref_parts(self):
1906 1908 return self.org_ref.split(':')
1907 1909
1908 1910 @property
1909 1911 def other_ref_parts(self):
1910 1912 return self.other_ref.split(':')
1911 1913
1912 1914 author = relationship('User', lazy='joined')
1913 1915 reviewers = relationship('PullRequestReviewers',
1914 1916 cascade="all, delete, delete-orphan")
1915 1917 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1916 1918 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1917 1919 statuses = relationship('ChangesetStatus')
1918 1920 comments = relationship('ChangesetComment',
1919 1921 cascade="all, delete, delete-orphan")
1920 1922
1921 1923 def is_closed(self):
1922 1924 return self.status == self.STATUS_CLOSED
1923 1925
1924 1926 @property
1925 1927 def last_review_status(self):
1926 1928 return self.statuses[-1].status if self.statuses else ''
1927 1929
1928 1930 def __json__(self):
1929 1931 return dict(
1930 1932 revisions=self.revisions
1931 1933 )
1932 1934
1933 1935
1934 1936 class PullRequestReviewers(Base, BaseModel):
1935 1937 __tablename__ = 'pull_request_reviewers'
1936 1938 __table_args__ = (
1937 1939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1938 1940 'mysql_charset': 'utf8'},
1939 1941 )
1940 1942
1941 1943 def __init__(self, user=None, pull_request=None):
1942 1944 self.user = user
1943 1945 self.pull_request = pull_request
1944 1946
1945 1947 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1946 1948 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1947 1949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1948 1950
1949 1951 user = relationship('User')
1950 1952 pull_request = relationship('PullRequest')
1951 1953
1952 1954
1953 1955 class Notification(Base, BaseModel):
1954 1956 __tablename__ = 'notifications'
1955 1957 __table_args__ = (
1956 1958 Index('notification_type_idx', 'type'),
1957 1959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1958 1960 'mysql_charset': 'utf8'},
1959 1961 )
1960 1962
1961 1963 TYPE_CHANGESET_COMMENT = u'cs_comment'
1962 1964 TYPE_MESSAGE = u'message'
1963 1965 TYPE_MENTION = u'mention'
1964 1966 TYPE_REGISTRATION = u'registration'
1965 1967 TYPE_PULL_REQUEST = u'pull_request'
1966 1968 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1967 1969
1968 1970 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1969 1971 subject = Column('subject', Unicode(512), nullable=True)
1970 1972 body = Column('body', UnicodeText(50000), nullable=True)
1971 1973 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1972 1974 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1973 1975 type_ = Column('type', Unicode(256))
1974 1976
1975 1977 created_by_user = relationship('User')
1976 1978 notifications_to_users = relationship('UserNotification', lazy='joined',
1977 1979 cascade="all, delete, delete-orphan")
1978 1980
1979 1981 @property
1980 1982 def recipients(self):
1981 1983 return [x.user for x in UserNotification.query()\
1982 1984 .filter(UserNotification.notification == self)\
1983 1985 .order_by(UserNotification.user_id.asc()).all()]
1984 1986
1985 1987 @classmethod
1986 1988 def create(cls, created_by, subject, body, recipients, type_=None):
1987 1989 if type_ is None:
1988 1990 type_ = Notification.TYPE_MESSAGE
1989 1991
1990 1992 notification = cls()
1991 1993 notification.created_by_user = created_by
1992 1994 notification.subject = subject
1993 1995 notification.body = body
1994 1996 notification.type_ = type_
1995 1997 notification.created_on = datetime.datetime.now()
1996 1998
1997 1999 for u in recipients:
1998 2000 assoc = UserNotification()
1999 2001 assoc.notification = notification
2000 2002 u.notifications.append(assoc)
2001 2003 Session().add(notification)
2002 2004 return notification
2003 2005
2004 2006 @property
2005 2007 def description(self):
2006 2008 from rhodecode.model.notification import NotificationModel
2007 2009 return NotificationModel().make_description(self)
2008 2010
2009 2011
2010 2012 class UserNotification(Base, BaseModel):
2011 2013 __tablename__ = 'user_to_notification'
2012 2014 __table_args__ = (
2013 2015 UniqueConstraint('user_id', 'notification_id'),
2014 2016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2015 2017 'mysql_charset': 'utf8'}
2016 2018 )
2017 2019 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2018 2020 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2019 2021 read = Column('read', Boolean, default=False)
2020 2022 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2021 2023
2022 2024 user = relationship('User', lazy="joined")
2023 2025 notification = relationship('Notification', lazy="joined",
2024 2026 order_by=lambda: Notification.created_on.desc(),)
2025 2027
2026 2028 def mark_as_read(self):
2027 2029 self.read = True
2028 2030 Session().add(self)
2029 2031
2030 2032
2031 2033 class DbMigrateVersion(Base, BaseModel):
2032 2034 __tablename__ = 'db_migrate_version'
2033 2035 __table_args__ = (
2034 2036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2035 2037 'mysql_charset': 'utf8'},
2036 2038 )
2037 2039 repository_id = Column('repository_id', String(250), primary_key=True)
2038 2040 repository_path = Column('repository_path', Text)
2039 2041 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now