##// END OF EJS Templates
sphinx doc cleanup
marcink -
r1819:9451a569 beta
parent child Browse files
Show More
@@ -1,1097 +1,1097
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) 2009-2011 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 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from vcs import get_backend
38 38 from vcs.utils.helpers import get_scm
39 39 from vcs.exceptions import VCSError
40 40 from vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
43 43 generate_api_key, safe_unicode
44 44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 45 from rhodecode.lib.compat import json
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48 from rhodecode.lib.caching_query import FromCache
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 class ModelSerializer(json.JSONEncoder):
58 58 """
59 59 Simple Serializer for JSON,
60 60
61 61 usage::
62 62
63 63 to make object customized for serialization implement a __json__
64 64 method that will return a dict for serialization into json
65 65
66 66 example::
67 67
68 68 class Task(object):
69 69
70 70 def __init__(self, name, value):
71 71 self.name = name
72 72 self.value = value
73 73
74 74 def __json__(self):
75 75 return dict(name=self.name,
76 76 value=self.value)
77 77
78 78 """
79 79
80 80 def default(self, obj):
81 81
82 82 if hasattr(obj, '__json__'):
83 83 return obj.__json__()
84 84 else:
85 85 return json.JSONEncoder.default(self, obj)
86 86
87 87 class BaseModel(object):
88 88 """Base Model for all classess
89 89
90 90 """
91 91
92 92 @classmethod
93 93 def _get_keys(cls):
94 94 """return column names for this model """
95 95 return class_mapper(cls).c.keys()
96 96
97 97 def get_dict(self):
98 98 """return dict with keys and values corresponding
99 99 to this model data """
100 100
101 101 d = {}
102 102 for k in self._get_keys():
103 103 d[k] = getattr(self, k)
104 104 return d
105 105
106 106 def get_appstruct(self):
107 107 """return list with keys and values tupples corresponding
108 108 to this model data """
109 109
110 110 l = []
111 111 for k in self._get_keys():
112 112 l.append((k, getattr(self, k),))
113 113 return l
114 114
115 115 def populate_obj(self, populate_dict):
116 116 """populate model with data from given populate_dict"""
117 117
118 118 for k in self._get_keys():
119 119 if k in populate_dict:
120 120 setattr(self, k, populate_dict[k])
121 121
122 122 @classmethod
123 123 def query(cls):
124 124 return Session.query(cls)
125 125
126 126 @classmethod
127 127 def get(cls, id_):
128 128 if id_:
129 129 return cls.query().get(id_)
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 Session.commit()
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
145 145 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 146 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147 147 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
148 148
149 149 def __init__(self, k='', v=''):
150 150 self.app_settings_name = k
151 151 self.app_settings_value = v
152 152
153 153
154 154 @validates('_app_settings_value')
155 155 def validate_settings_value(self, key, val):
156 156 assert type(val) == unicode
157 157 return val
158 158
159 159 @hybrid_property
160 160 def app_settings_value(self):
161 161 v = self._app_settings_value
162 162 if v == 'ldap_active':
163 163 v = str2bool(v)
164 164 return v
165 165
166 166 @app_settings_value.setter
167 167 def app_settings_value(self, val):
168 168 """
169 169 Setter that will always make sure we use unicode in app_settings_value
170 170
171 171 :param val:
172 172 """
173 173 self._app_settings_value = safe_unicode(val)
174 174
175 175 def __repr__(self):
176 176 return "<%s('%s:%s')>" % (self.__class__.__name__,
177 177 self.app_settings_name, self.app_settings_value)
178 178
179 179
180 180 @classmethod
181 181 def get_by_name(cls, ldap_key):
182 182 return cls.query()\
183 183 .filter(cls.app_settings_name == ldap_key).scalar()
184 184
185 185 @classmethod
186 186 def get_app_settings(cls, cache=False):
187 187
188 188 ret = cls.query()
189 189
190 190 if cache:
191 191 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
192 192
193 193 if not ret:
194 194 raise Exception('Could not get application settings !')
195 195 settings = {}
196 196 for each in ret:
197 197 settings['rhodecode_' + each.app_settings_name] = \
198 198 each.app_settings_value
199 199
200 200 return settings
201 201
202 202 @classmethod
203 203 def get_ldap_settings(cls, cache=False):
204 204 ret = cls.query()\
205 205 .filter(cls.app_settings_name.startswith('ldap_')).all()
206 206 fd = {}
207 207 for row in ret:
208 208 fd.update({row.app_settings_name:row.app_settings_value})
209 209
210 210 return fd
211 211
212 212
213 213 class RhodeCodeUi(Base, BaseModel):
214 214 __tablename__ = 'rhodecode_ui'
215 215 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
216 216
217 217 HOOK_UPDATE = 'changegroup.update'
218 218 HOOK_REPO_SIZE = 'changegroup.repo_size'
219 219 HOOK_PUSH = 'pretxnchangegroup.push_logger'
220 220 HOOK_PULL = 'preoutgoing.pull_logger'
221 221
222 222 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
223 223 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 224 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 225 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
226 226 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
227 227
228 228
229 229 @classmethod
230 230 def get_by_key(cls, key):
231 231 return cls.query().filter(cls.ui_key == key)
232 232
233 233
234 234 @classmethod
235 235 def get_builtin_hooks(cls):
236 236 q = cls.query()
237 237 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
238 238 cls.HOOK_REPO_SIZE,
239 239 cls.HOOK_PUSH, cls.HOOK_PULL]))
240 240 return q.all()
241 241
242 242 @classmethod
243 243 def get_custom_hooks(cls):
244 244 q = cls.query()
245 245 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
246 246 cls.HOOK_REPO_SIZE,
247 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 248 q = q.filter(cls.ui_section == 'hooks')
249 249 return q.all()
250 250
251 251 @classmethod
252 252 def create_or_update_hook(cls, key, val):
253 253 new_ui = cls.get_by_key(key).scalar() or cls()
254 254 new_ui.ui_section = 'hooks'
255 255 new_ui.ui_active = True
256 256 new_ui.ui_key = key
257 257 new_ui.ui_value = val
258 258
259 259 Session.add(new_ui)
260 260 Session.commit()
261 261
262 262
263 263 class User(Base, BaseModel):
264 264 __tablename__ = 'users'
265 265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
266 266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
270 270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
271 271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 273 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
275 275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277 277
278 278 user_log = relationship('UserLog', cascade='all')
279 279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
280 280
281 281 repositories = relationship('Repository')
282 282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
283 283 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
284 284
285 285 group_member = relationship('UsersGroupMember', cascade='all')
286 286
287 287 @property
288 288 def full_contact(self):
289 289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
290 290
291 291 @property
292 292 def short_contact(self):
293 293 return '%s %s' % (self.name, self.lastname)
294 294
295 295 @property
296 296 def is_admin(self):
297 297 return self.admin
298 298
299 299 def __repr__(self):
300 300 try:
301 301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 302 self.user_id, self.username)
303 303 except:
304 304 return self.__class__.__name__
305 305
306 306 @classmethod
307 307 def get_by_username(cls, username, case_insensitive=False):
308 308 if case_insensitive:
309 309 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
310 310 else:
311 311 return Session.query(cls).filter(cls.username == username).scalar()
312 312
313 313 @classmethod
314 314 def get_by_api_key(cls, api_key):
315 315 return cls.query().filter(cls.api_key == api_key).one()
316 316
317 317 def update_lastlogin(self):
318 318 """Update user lastlogin"""
319 319
320 320 self.last_login = datetime.datetime.now()
321 321 Session.add(self)
322 322 Session.commit()
323 323 log.debug('updated user %s lastlogin', self.username)
324 324
325 325 @classmethod
326 326 def create(cls, form_data):
327 327 from rhodecode.lib.auth import get_crypt_password
328 328
329 329 try:
330 330 new_user = cls()
331 331 for k, v in form_data.items():
332 332 if k == 'password':
333 333 v = get_crypt_password(v)
334 334 setattr(new_user, k, v)
335 335
336 336 new_user.api_key = generate_api_key(form_data['username'])
337 337 Session.add(new_user)
338 338 Session.commit()
339 339 return new_user
340 340 except:
341 341 log.error(traceback.format_exc())
342 342 Session.rollback()
343 343 raise
344 344
345 345 class UserLog(Base, BaseModel):
346 346 __tablename__ = 'user_logs'
347 347 __table_args__ = {'extend_existing':True}
348 348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
350 350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
351 351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
354 354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
355 355
356 356 @property
357 357 def action_as_day(self):
358 358 return date(*self.action_date.timetuple()[:3])
359 359
360 360 user = relationship('User')
361 361 repository = relationship('Repository')
362 362
363 363
364 364 class UsersGroup(Base, BaseModel):
365 365 __tablename__ = 'users_groups'
366 366 __table_args__ = {'extend_existing':True}
367 367
368 368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
369 369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
370 370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
371 371
372 372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
373 373
374 374 def __repr__(self):
375 375 return '<userGroup(%s)>' % (self.users_group_name)
376 376
377 377 @classmethod
378 378 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
379 379 if case_insensitive:
380 380 gr = cls.query()\
381 381 .filter(cls.users_group_name.ilike(group_name))
382 382 else:
383 383 gr = cls.query()\
384 384 .filter(cls.users_group_name == group_name)
385 385 if cache:
386 386 gr = gr.options(FromCache("sql_cache_short",
387 387 "get_user_%s" % group_name))
388 388 return gr.scalar()
389 389
390 390
391 391 @classmethod
392 392 def get(cls, users_group_id, cache=False):
393 393 users_group = cls.query()
394 394 if cache:
395 395 users_group = users_group.options(FromCache("sql_cache_short",
396 396 "get_users_group_%s" % users_group_id))
397 397 return users_group.get(users_group_id)
398 398
399 399 @classmethod
400 400 def create(cls, form_data):
401 401 try:
402 402 new_users_group = cls()
403 403 for k, v in form_data.items():
404 404 setattr(new_users_group, k, v)
405 405
406 406 Session.add(new_users_group)
407 407 Session.commit()
408 408 return new_users_group
409 409 except:
410 410 log.error(traceback.format_exc())
411 411 Session.rollback()
412 412 raise
413 413
414 414 @classmethod
415 415 def update(cls, users_group_id, form_data):
416 416
417 417 try:
418 418 users_group = cls.get(users_group_id, cache=False)
419 419
420 420 for k, v in form_data.items():
421 421 if k == 'users_group_members':
422 422 users_group.members = []
423 423 Session.flush()
424 424 members_list = []
425 425 if v:
426 426 v = [v] if isinstance(v, basestring) else v
427 427 for u_id in set(v):
428 428 member = UsersGroupMember(users_group_id, u_id)
429 429 members_list.append(member)
430 430 setattr(users_group, 'members', members_list)
431 431 setattr(users_group, k, v)
432 432
433 433 Session.add(users_group)
434 434 Session.commit()
435 435 except:
436 436 log.error(traceback.format_exc())
437 437 Session.rollback()
438 438 raise
439 439
440 440 @classmethod
441 441 def delete(cls, users_group_id):
442 442 try:
443 443
444 444 # check if this group is not assigned to repo
445 445 assigned_groups = UsersGroupRepoToPerm.query()\
446 446 .filter(UsersGroupRepoToPerm.users_group_id ==
447 447 users_group_id).all()
448 448
449 449 if assigned_groups:
450 450 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
451 451 assigned_groups)
452 452
453 453 users_group = cls.get(users_group_id, cache=False)
454 454 Session.delete(users_group)
455 455 Session.commit()
456 456 except:
457 457 log.error(traceback.format_exc())
458 458 Session.rollback()
459 459 raise
460 460
461 461 class UsersGroupMember(Base, BaseModel):
462 462 __tablename__ = 'users_groups_members'
463 463 __table_args__ = {'extend_existing':True}
464 464
465 465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
467 467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 468
469 469 user = relationship('User', lazy='joined')
470 470 users_group = relationship('UsersGroup')
471 471
472 472 def __init__(self, gr_id='', u_id=''):
473 473 self.users_group_id = gr_id
474 474 self.user_id = u_id
475 475
476 476 @staticmethod
477 477 def add_user_to_group(group, user):
478 478 ugm = UsersGroupMember()
479 479 ugm.users_group = group
480 480 ugm.user = user
481 481 Session.add(ugm)
482 482 Session.commit()
483 483 return ugm
484 484
485 485 class Repository(Base, BaseModel):
486 486 __tablename__ = 'repositories'
487 487 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
488 488
489 489 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 490 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 491 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
492 492 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
493 493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
494 494 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
495 495 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
496 496 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
497 497 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
498 498 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
499 499
500 500 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
501 501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
502 502
503 503
504 504 user = relationship('User')
505 505 fork = relationship('Repository', remote_side=repo_id)
506 506 group = relationship('RepoGroup')
507 507 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
508 508 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
509 509 stats = relationship('Statistics', cascade='all', uselist=False)
510 510
511 511 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
512 512
513 513 logs = relationship('UserLog', cascade='all')
514 514
515 515 def __repr__(self):
516 516 return "<%s('%s:%s')>" % (self.__class__.__name__,
517 517 self.repo_id, self.repo_name)
518 518
519 519 @classmethod
520 520 def url_sep(cls):
521 521 return '/'
522 522
523 523 @classmethod
524 524 def get_by_repo_name(cls, repo_name):
525 525 q = Session.query(cls).filter(cls.repo_name == repo_name)
526 526 q = q.options(joinedload(Repository.fork))\
527 527 .options(joinedload(Repository.user))\
528 528 .options(joinedload(Repository.group))
529 529 return q.one()
530 530
531 531 @classmethod
532 532 def get_repo_forks(cls, repo_id):
533 533 return cls.query().filter(Repository.fork_id == repo_id)
534 534
535 535 @classmethod
536 536 def base_path(cls):
537 537 """
538 538 Returns base path when all repos are stored
539 539
540 540 :param cls:
541 541 """
542 542 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
543 543 cls.url_sep())
544 544 q.options(FromCache("sql_cache_short", "repository_repo_path"))
545 545 return q.one().ui_value
546 546
547 547 @property
548 548 def just_name(self):
549 549 return self.repo_name.split(Repository.url_sep())[-1]
550 550
551 551 @property
552 552 def groups_with_parents(self):
553 553 groups = []
554 554 if self.group is None:
555 555 return groups
556 556
557 557 cur_gr = self.group
558 558 groups.insert(0, cur_gr)
559 559 while 1:
560 560 gr = getattr(cur_gr, 'parent_group', None)
561 561 cur_gr = cur_gr.parent_group
562 562 if gr is None:
563 563 break
564 564 groups.insert(0, gr)
565 565
566 566 return groups
567 567
568 568 @property
569 569 def groups_and_repo(self):
570 570 return self.groups_with_parents, self.just_name
571 571
572 572 @LazyProperty
573 573 def repo_path(self):
574 574 """
575 575 Returns base full path for that repository means where it actually
576 576 exists on a filesystem
577 577 """
578 578 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
579 579 Repository.url_sep())
580 580 q.options(FromCache("sql_cache_short", "repository_repo_path"))
581 581 return q.one().ui_value
582 582
583 583 @property
584 584 def repo_full_path(self):
585 585 p = [self.repo_path]
586 586 # we need to split the name by / since this is how we store the
587 587 # names in the database, but that eventually needs to be converted
588 588 # into a valid system path
589 589 p += self.repo_name.split(Repository.url_sep())
590 590 return os.path.join(*p)
591 591
592 592 def get_new_name(self, repo_name):
593 593 """
594 594 returns new full repository name based on assigned group and new new
595 595
596 596 :param group_name:
597 597 """
598 598 path_prefix = self.group.full_path_splitted if self.group else []
599 599 return Repository.url_sep().join(path_prefix + [repo_name])
600 600
601 601 @property
602 602 def _ui(self):
603 603 """
604 604 Creates an db based ui object for this repository
605 605 """
606 606 from mercurial import ui
607 607 from mercurial import config
608 608 baseui = ui.ui()
609 609
610 610 #clean the baseui object
611 611 baseui._ocfg = config.config()
612 612 baseui._ucfg = config.config()
613 613 baseui._tcfg = config.config()
614 614
615 615
616 616 ret = RhodeCodeUi.query()\
617 617 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
618 618
619 619 hg_ui = ret
620 620 for ui_ in hg_ui:
621 621 if ui_.ui_active:
622 622 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
623 623 ui_.ui_key, ui_.ui_value)
624 624 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
625 625
626 626 return baseui
627 627
628 628 @classmethod
629 629 def is_valid(cls, repo_name):
630 630 """
631 631 returns True if given repo name is a valid filesystem repository
632 632
633 @param cls:
634 @param repo_name:
633 :param cls:
634 :param repo_name:
635 635 """
636 636 from rhodecode.lib.utils import is_valid_repo
637 637
638 638 return is_valid_repo(repo_name, cls.base_path())
639 639
640 640
641 641 #==========================================================================
642 642 # SCM PROPERTIES
643 643 #==========================================================================
644 644
645 645 def get_changeset(self, rev):
646 646 return get_changeset_safe(self.scm_instance, rev)
647 647
648 648 @property
649 649 def tip(self):
650 650 return self.get_changeset('tip')
651 651
652 652 @property
653 653 def author(self):
654 654 return self.tip.author
655 655
656 656 @property
657 657 def last_change(self):
658 658 return self.scm_instance.last_change
659 659
660 660 #==========================================================================
661 661 # SCM CACHE INSTANCE
662 662 #==========================================================================
663 663
664 664 @property
665 665 def invalidate(self):
666 666 return CacheInvalidation.invalidate(self.repo_name)
667 667
668 668 def set_invalidate(self):
669 669 """
670 670 set a cache for invalidation for this instance
671 671 """
672 672 CacheInvalidation.set_invalidate(self.repo_name)
673 673
674 674 @LazyProperty
675 675 def scm_instance(self):
676 676 return self.__get_instance()
677 677
678 678 @property
679 679 def scm_instance_cached(self):
680 680 @cache_region('long_term')
681 681 def _c(repo_name):
682 682 return self.__get_instance()
683 683 rn = self.repo_name
684 684
685 685 inv = self.invalidate
686 686 if inv is not None:
687 687 region_invalidate(_c, None, rn)
688 688 # update our cache
689 689 CacheInvalidation.set_valid(inv.cache_key)
690 690 return _c(rn)
691 691
692 692 def __get_instance(self):
693 693
694 694 repo_full_path = self.repo_full_path
695 695
696 696 try:
697 697 alias = get_scm(repo_full_path)[0]
698 698 log.debug('Creating instance of %s repository', alias)
699 699 backend = get_backend(alias)
700 700 except VCSError:
701 701 log.error(traceback.format_exc())
702 702 log.error('Perhaps this repository is in db and not in '
703 703 'filesystem run rescan repositories with '
704 704 '"destroy old data " option from admin panel')
705 705 return
706 706
707 707 if alias == 'hg':
708 708
709 709 repo = backend(safe_str(repo_full_path), create=False,
710 710 baseui=self._ui)
711 711 # skip hidden web repository
712 712 if repo._get_hidden():
713 713 return
714 714 else:
715 715 repo = backend(repo_full_path, create=False)
716 716
717 717 return repo
718 718
719 719
720 720 class RepoGroup(Base, BaseModel):
721 721 __tablename__ = 'groups'
722 722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
723 723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
724 724 __mapper_args__ = {'order_by':'group_name'}
725 725
726 726 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
727 727 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
728 728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
729 729 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
730 730
731 731 parent_group = relationship('RepoGroup', remote_side=group_id)
732 732
733 733
734 734 def __init__(self, group_name='', parent_group=None):
735 735 self.group_name = group_name
736 736 self.parent_group = parent_group
737 737
738 738 def __repr__(self):
739 739 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
740 740 self.group_name)
741 741
742 742 @classmethod
743 743 def groups_choices(cls):
744 744 from webhelpers.html import literal as _literal
745 745 repo_groups = [('', '')]
746 746 sep = ' &raquo; '
747 747 _name = lambda k: _literal(sep.join(k))
748 748
749 749 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
750 750 for x in cls.query().all()])
751 751
752 752 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
753 753 return repo_groups
754 754
755 755 @classmethod
756 756 def url_sep(cls):
757 757 return '/'
758 758
759 759 @classmethod
760 760 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
761 761 if case_insensitive:
762 762 gr = cls.query()\
763 763 .filter(cls.group_name.ilike(group_name))
764 764 else:
765 765 gr = cls.query()\
766 766 .filter(cls.group_name == group_name)
767 767 if cache:
768 768 gr = gr.options(FromCache("sql_cache_short",
769 769 "get_group_%s" % group_name))
770 770 return gr.scalar()
771 771
772 772 @property
773 773 def parents(self):
774 774 parents_recursion_limit = 5
775 775 groups = []
776 776 if self.parent_group is None:
777 777 return groups
778 778 cur_gr = self.parent_group
779 779 groups.insert(0, cur_gr)
780 780 cnt = 0
781 781 while 1:
782 782 cnt += 1
783 783 gr = getattr(cur_gr, 'parent_group', None)
784 784 cur_gr = cur_gr.parent_group
785 785 if gr is None:
786 786 break
787 787 if cnt == parents_recursion_limit:
788 788 # this will prevent accidental infinit loops
789 789 log.error('group nested more than %s' %
790 790 parents_recursion_limit)
791 791 break
792 792
793 793 groups.insert(0, gr)
794 794 return groups
795 795
796 796 @property
797 797 def children(self):
798 798 return Group.query().filter(Group.parent_group == self)
799 799
800 800 @property
801 801 def name(self):
802 802 return self.group_name.split(Group.url_sep())[-1]
803 803
804 804 @property
805 805 def full_path(self):
806 806 return self.group_name
807 807
808 808 @property
809 809 def full_path_splitted(self):
810 810 return self.group_name.split(Group.url_sep())
811 811
812 812 @property
813 813 def repositories(self):
814 814 return Repository.query().filter(Repository.group == self)
815 815
816 816 @property
817 817 def repositories_recursive_count(self):
818 818 cnt = self.repositories.count()
819 819
820 820 def children_count(group):
821 821 cnt = 0
822 822 for child in group.children:
823 823 cnt += child.repositories.count()
824 824 cnt += children_count(child)
825 825 return cnt
826 826
827 827 return cnt + children_count(self)
828 828
829 829
830 830 def get_new_name(self, group_name):
831 831 """
832 832 returns new full group name based on parent and new name
833 833
834 834 :param group_name:
835 835 """
836 836 path_prefix = (self.parent_group.full_path_splitted if
837 837 self.parent_group else [])
838 838 return Group.url_sep().join(path_prefix + [group_name])
839 839
840 840
841 841 class Permission(Base, BaseModel):
842 842 __tablename__ = 'permissions'
843 843 __table_args__ = {'extend_existing':True}
844 844 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
845 845 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
846 846 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
847 847
848 848 def __repr__(self):
849 849 return "<%s('%s:%s')>" % (self.__class__.__name__,
850 850 self.permission_id, self.permission_name)
851 851
852 852 @classmethod
853 853 def get_by_key(cls, key):
854 854 return cls.query().filter(cls.permission_name == key).scalar()
855 855
856 856 class UserRepoToPerm(Base, BaseModel):
857 857 __tablename__ = 'repo_to_perm'
858 858 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
859 859 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
860 860 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
861 861 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
862 862 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
863 863
864 864 user = relationship('User')
865 865 permission = relationship('Permission')
866 866 repository = relationship('Repository')
867 867
868 868 class UserToPerm(Base, BaseModel):
869 869 __tablename__ = 'user_to_perm'
870 870 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
871 871 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
873 873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
874 874
875 875 user = relationship('User')
876 876 permission = relationship('Permission')
877 877
878 878 @classmethod
879 879 def has_perm(cls, user_id, perm):
880 880 if not isinstance(perm, Permission):
881 881 raise Exception('perm needs to be an instance of Permission class')
882 882
883 883 return cls.query().filter(cls.user_id == user_id)\
884 884 .filter(cls.permission == perm).scalar() is not None
885 885
886 886 @classmethod
887 887 def grant_perm(cls, user_id, perm):
888 888 if not isinstance(perm, Permission):
889 889 raise Exception('perm needs to be an instance of Permission class')
890 890
891 891 new = cls()
892 892 new.user_id = user_id
893 893 new.permission = perm
894 894 try:
895 895 Session.add(new)
896 896 Session.commit()
897 897 except:
898 898 Session.rollback()
899 899
900 900
901 901 @classmethod
902 902 def revoke_perm(cls, user_id, perm):
903 903 if not isinstance(perm, Permission):
904 904 raise Exception('perm needs to be an instance of Permission class')
905 905
906 906 try:
907 907 cls.query().filter(cls.user_id == user_id)\
908 908 .filter(cls.permission == perm).delete()
909 909 Session.commit()
910 910 except:
911 911 Session.rollback()
912 912
913 913 class UsersGroupRepoToPerm(Base, BaseModel):
914 914 __tablename__ = 'users_group_repo_to_perm'
915 915 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
916 916 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
917 917 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
918 918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
919 919 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
920 920
921 921 users_group = relationship('UsersGroup')
922 922 permission = relationship('Permission')
923 923 repository = relationship('Repository')
924 924
925 925 def __repr__(self):
926 926 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
927 927
928 928 class UsersGroupToPerm(Base, BaseModel):
929 929 __tablename__ = 'users_group_to_perm'
930 930 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 931 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
932 932 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
933 933
934 934 users_group = relationship('UsersGroup')
935 935 permission = relationship('Permission')
936 936
937 937
938 938 @classmethod
939 939 def has_perm(cls, users_group_id, perm):
940 940 if not isinstance(perm, Permission):
941 941 raise Exception('perm needs to be an instance of Permission class')
942 942
943 943 return cls.query().filter(cls.users_group_id ==
944 944 users_group_id)\
945 945 .filter(cls.permission == perm)\
946 946 .scalar() is not None
947 947
948 948 @classmethod
949 949 def grant_perm(cls, users_group_id, perm):
950 950 if not isinstance(perm, Permission):
951 951 raise Exception('perm needs to be an instance of Permission class')
952 952
953 953 new = cls()
954 954 new.users_group_id = users_group_id
955 955 new.permission = perm
956 956 try:
957 957 Session.add(new)
958 958 Session.commit()
959 959 except:
960 960 Session.rollback()
961 961
962 962
963 963 @classmethod
964 964 def revoke_perm(cls, users_group_id, perm):
965 965 if not isinstance(perm, Permission):
966 966 raise Exception('perm needs to be an instance of Permission class')
967 967
968 968 try:
969 969 cls.query().filter(cls.users_group_id == users_group_id)\
970 970 .filter(cls.permission == perm).delete()
971 971 Session.commit()
972 972 except:
973 973 Session.rollback()
974 974
975 975
976 976 class UserRepoGroupToPerm(Base, BaseModel):
977 977 __tablename__ = 'group_to_perm'
978 978 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
979 979
980 980 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
981 981 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
982 982 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
983 983 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
984 984
985 985 user = relationship('User')
986 986 permission = relationship('Permission')
987 987 group = relationship('RepoGroup')
988 988
989 989 class Statistics(Base, BaseModel):
990 990 __tablename__ = 'statistics'
991 991 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
992 992 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
993 993 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
994 994 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
995 995 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
996 996 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
997 997 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
998 998
999 999 repository = relationship('Repository', single_parent=True)
1000 1000
1001 1001 class UserFollowing(Base, BaseModel):
1002 1002 __tablename__ = 'user_followings'
1003 1003 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1004 1004 UniqueConstraint('user_id', 'follows_user_id')
1005 1005 , {'extend_existing':True})
1006 1006
1007 1007 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1008 1008 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1009 1009 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1010 1010 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1011 1011 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1012 1012
1013 1013 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1014 1014
1015 1015 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1016 1016 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1017 1017
1018 1018
1019 1019 @classmethod
1020 1020 def get_repo_followers(cls, repo_id):
1021 1021 return cls.query().filter(cls.follows_repo_id == repo_id)
1022 1022
1023 1023 class CacheInvalidation(Base, BaseModel):
1024 1024 __tablename__ = 'cache_invalidation'
1025 1025 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1026 1026 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1027 1027 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1028 1028 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1029 1029 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1030 1030
1031 1031
1032 1032 def __init__(self, cache_key, cache_args=''):
1033 1033 self.cache_key = cache_key
1034 1034 self.cache_args = cache_args
1035 1035 self.cache_active = False
1036 1036
1037 1037 def __repr__(self):
1038 1038 return "<%s('%s:%s')>" % (self.__class__.__name__,
1039 1039 self.cache_id, self.cache_key)
1040 1040
1041 1041 @classmethod
1042 1042 def invalidate(cls, key):
1043 1043 """
1044 1044 Returns Invalidation object if this given key should be invalidated
1045 1045 None otherwise. `cache_active = False` means that this cache
1046 1046 state is not valid and needs to be invalidated
1047 1047
1048 1048 :param key:
1049 1049 """
1050 1050 return cls.query()\
1051 1051 .filter(CacheInvalidation.cache_key == key)\
1052 1052 .filter(CacheInvalidation.cache_active == False)\
1053 1053 .scalar()
1054 1054
1055 1055 @classmethod
1056 1056 def set_invalidate(cls, key):
1057 1057 """
1058 1058 Mark this Cache key for invalidation
1059 1059
1060 1060 :param key:
1061 1061 """
1062 1062
1063 1063 log.debug('marking %s for invalidation' % key)
1064 1064 inv_obj = Session.query(cls)\
1065 1065 .filter(cls.cache_key == key).scalar()
1066 1066 if inv_obj:
1067 1067 inv_obj.cache_active = False
1068 1068 else:
1069 1069 log.debug('cache key not found in invalidation db -> creating one')
1070 1070 inv_obj = CacheInvalidation(key)
1071 1071
1072 1072 try:
1073 1073 Session.add(inv_obj)
1074 1074 Session.commit()
1075 1075 except Exception:
1076 1076 log.error(traceback.format_exc())
1077 1077 Session.rollback()
1078 1078
1079 1079 @classmethod
1080 1080 def set_valid(cls, key):
1081 1081 """
1082 1082 Mark this cache key as active and currently cached
1083 1083
1084 1084 :param key:
1085 1085 """
1086 1086 inv_obj = Session.query(CacheInvalidation)\
1087 1087 .filter(CacheInvalidation.cache_key == key).scalar()
1088 1088 inv_obj.cache_active = True
1089 1089 Session.add(inv_obj)
1090 1090 Session.commit()
1091 1091
1092 1092 class DbMigrateVersion(Base, BaseModel):
1093 1093 __tablename__ = 'db_migrate_version'
1094 1094 __table_args__ = {'extend_existing':True}
1095 1095 repository_id = Column('repository_id', String(250), primary_key=True)
1096 1096 repository_path = Column('repository_path', Text)
1097 1097 version = Column('version', Integer)
@@ -1,1122 +1,1122
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) 2009-2011 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
31 31 from sqlalchemy import *
32 32 from sqlalchemy.ext.hybrid import hybrid_property
33 33 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 34 from beaker.cache import cache_region, region_invalidate
35 35
36 36 from vcs import get_backend
37 37 from vcs.utils.helpers import get_scm
38 38 from vcs.exceptions import VCSError
39 39 from vcs.utils.lazy import LazyProperty
40 40
41 41 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 42 from rhodecode.lib.exceptions import UsersGroupsAssignedException
43 43 from rhodecode.lib.compat import json
44 44 from rhodecode.lib.caching_query import FromCache
45 45
46 46 from rhodecode.model.meta import Base, Session
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 #==============================================================================
51 51 # BASE CLASSES
52 52 #==============================================================================
53 53
54 54
55 55 class ModelSerializer(json.JSONEncoder):
56 56 """
57 57 Simple Serializer for JSON,
58 58
59 59 usage::
60 60
61 61 to make object customized for serialization implement a __json__
62 62 method that will return a dict for serialization into json
63 63
64 64 example::
65 65
66 66 class Task(object):
67 67
68 68 def __init__(self, name, value):
69 69 self.name = name
70 70 self.value = value
71 71
72 72 def __json__(self):
73 73 return dict(name=self.name,
74 74 value=self.value)
75 75
76 76 """
77 77
78 78 def default(self, obj):
79 79
80 80 if hasattr(obj, '__json__'):
81 81 return obj.__json__()
82 82 else:
83 83 return json.JSONEncoder.default(self, obj)
84 84
85 85
86 86 class BaseModel(object):
87 87 """
88 88 Base Model for all classess
89 89 """
90 90
91 91 @classmethod
92 92 def _get_keys(cls):
93 93 """return column names for this model """
94 94 return class_mapper(cls).c.keys()
95 95
96 96 def get_dict(self, serialized=False):
97 97 """
98 98 return dict with keys and values corresponding
99 99 to this model data
100 100 """
101 101
102 102 d = {}
103 103 for k in self._get_keys():
104 104 d[k] = getattr(self, k)
105 105
106 106 # also use __json__() if present to get additional fields
107 107 if hasattr(self, '__json__'):
108 108 for k, val in self.__json__().iteritems():
109 109 d[k] = val
110 110 return d
111 111
112 112 def get_appstruct(self):
113 113 """return list with keys and values tupples corresponding
114 114 to this model data """
115 115
116 116 l = []
117 117 for k in self._get_keys():
118 118 l.append((k, getattr(self, k),))
119 119 return l
120 120
121 121 def populate_obj(self, populate_dict):
122 122 """populate model with data from given populate_dict"""
123 123
124 124 for k in self._get_keys():
125 125 if k in populate_dict:
126 126 setattr(self, k, populate_dict[k])
127 127
128 128 @classmethod
129 129 def query(cls):
130 130 return Session.query(cls)
131 131
132 132 @classmethod
133 133 def get(cls, id_):
134 134 if id_:
135 135 return cls.query().get(id_)
136 136
137 137 @classmethod
138 138 def getAll(cls):
139 139 return cls.query().all()
140 140
141 141 @classmethod
142 142 def delete(cls, id_):
143 143 obj = cls.query().get(id_)
144 144 Session.delete(obj)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
150 150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
151 151 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153 153
154 154 def __init__(self, k='', v=''):
155 155 self.app_settings_name = k
156 156 self.app_settings_value = v
157 157
158 158
159 159 @validates('_app_settings_value')
160 160 def validate_settings_value(self, key, val):
161 161 assert type(val) == unicode
162 162 return val
163 163
164 164 @hybrid_property
165 165 def app_settings_value(self):
166 166 v = self._app_settings_value
167 167 if v == 'ldap_active':
168 168 v = str2bool(v)
169 169 return v
170 170
171 171 @app_settings_value.setter
172 172 def app_settings_value(self, val):
173 173 """
174 174 Setter that will always make sure we use unicode in app_settings_value
175 175
176 176 :param val:
177 177 """
178 178 self._app_settings_value = safe_unicode(val)
179 179
180 180 def __repr__(self):
181 181 return "<%s('%s:%s')>" % (self.__class__.__name__,
182 182 self.app_settings_name, self.app_settings_value)
183 183
184 184 @classmethod
185 185 def get_by_name(cls, ldap_key):
186 186 return cls.query()\
187 187 .filter(cls.app_settings_name == ldap_key).scalar()
188 188
189 189 @classmethod
190 190 def get_app_settings(cls, cache=False):
191 191
192 192 ret = cls.query()
193 193
194 194 if cache:
195 195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
196 196
197 197 if not ret:
198 198 raise Exception('Could not get application settings !')
199 199 settings = {}
200 200 for each in ret:
201 201 settings['rhodecode_' + each.app_settings_name] = \
202 202 each.app_settings_value
203 203
204 204 return settings
205 205
206 206 @classmethod
207 207 def get_ldap_settings(cls, cache=False):
208 208 ret = cls.query()\
209 209 .filter(cls.app_settings_name.startswith('ldap_')).all()
210 210 fd = {}
211 211 for row in ret:
212 212 fd.update({row.app_settings_name:row.app_settings_value})
213 213
214 214 return fd
215 215
216 216
217 217 class RhodeCodeUi(Base, BaseModel):
218 218 __tablename__ = 'rhodecode_ui'
219 219 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
220 220
221 221 HOOK_UPDATE = 'changegroup.update'
222 222 HOOK_REPO_SIZE = 'changegroup.repo_size'
223 223 HOOK_PUSH = 'pretxnchangegroup.push_logger'
224 224 HOOK_PULL = 'preoutgoing.pull_logger'
225 225
226 226 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
227 227 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
228 228 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
229 229 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
230 230 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
231 231
232 232 @classmethod
233 233 def get_by_key(cls, key):
234 234 return cls.query().filter(cls.ui_key == key)
235 235
236 236 @classmethod
237 237 def get_builtin_hooks(cls):
238 238 q = cls.query()
239 239 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
240 240 cls.HOOK_REPO_SIZE,
241 241 cls.HOOK_PUSH, cls.HOOK_PULL]))
242 242 return q.all()
243 243
244 244 @classmethod
245 245 def get_custom_hooks(cls):
246 246 q = cls.query()
247 247 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
248 248 cls.HOOK_REPO_SIZE,
249 249 cls.HOOK_PUSH, cls.HOOK_PULL]))
250 250 q = q.filter(cls.ui_section == 'hooks')
251 251 return q.all()
252 252
253 253 @classmethod
254 254 def create_or_update_hook(cls, key, val):
255 255 new_ui = cls.get_by_key(key).scalar() or cls()
256 256 new_ui.ui_section = 'hooks'
257 257 new_ui.ui_active = True
258 258 new_ui.ui_key = key
259 259 new_ui.ui_value = val
260 260
261 261 Session.add(new_ui)
262 262
263 263
264 264 class User(Base, BaseModel):
265 265 __tablename__ = 'users'
266 266 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
267 267 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 268 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 270 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
271 271 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
272 272 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 273 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 274 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 275 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
276 276 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277 277 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 278
279 279 user_log = relationship('UserLog', cascade='all')
280 280 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
281 281
282 282 repositories = relationship('Repository')
283 283 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
284 284 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
285 285
286 286 group_member = relationship('UsersGroupMember', cascade='all')
287 287
288 288 notifications = relationship('UserNotification',)
289 289
290 290 @hybrid_property
291 291 def email(self):
292 292 return self._email
293 293
294 294 @email.setter
295 295 def email(self, val):
296 296 self._email = val.lower() if val else None
297 297
298 298 @property
299 299 def full_name(self):
300 300 return '%s %s' % (self.name, self.lastname)
301 301
302 302 @property
303 303 def full_contact(self):
304 304 return '%s %s <%s>' % (self.name, self.lastname, self.email)
305 305
306 306 @property
307 307 def short_contact(self):
308 308 return '%s %s' % (self.name, self.lastname)
309 309
310 310 @property
311 311 def is_admin(self):
312 312 return self.admin
313 313
314 314 def __repr__(self):
315 315 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
316 316 self.user_id, self.username)
317 317
318 318 @classmethod
319 319 def get_by_username(cls, username, case_insensitive=False, cache=False):
320 320 if case_insensitive:
321 321 q = cls.query().filter(cls.username.ilike(username))
322 322 else:
323 323 q = cls.query().filter(cls.username == username)
324 324
325 325 if cache:
326 326 q = q.options(FromCache("sql_cache_short",
327 327 "get_user_%s" % username))
328 328 return q.scalar()
329 329
330 330 @classmethod
331 331 def get_by_api_key(cls, api_key, cache=False):
332 332 q = cls.query().filter(cls.api_key == api_key)
333 333
334 334 if cache:
335 335 q = q.options(FromCache("sql_cache_short",
336 336 "get_api_key_%s" % api_key))
337 337 return q.scalar()
338 338
339 339 @classmethod
340 340 def get_by_email(cls, email, case_insensitive=False, cache=False):
341 341 if case_insensitive:
342 342 q = cls.query().filter(cls.email.ilike(email))
343 343 else:
344 344 q = cls.query().filter(cls.email == email)
345 345
346 346 if cache:
347 347 q = q.options(FromCache("sql_cache_short",
348 348 "get_api_key_%s" % email))
349 349 return q.scalar()
350 350
351 351 def update_lastlogin(self):
352 352 """Update user lastlogin"""
353 353 self.last_login = datetime.datetime.now()
354 354 Session.add(self)
355 355 log.debug('updated user %s lastlogin', self.username)
356 356
357 357 def __json__(self):
358 358 return dict(email=self.email,
359 359 full_name=self.full_name)
360 360
361 361
362 362 class UserLog(Base, BaseModel):
363 363 __tablename__ = 'user_logs'
364 364 __table_args__ = {'extend_existing':True}
365 365 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
366 366 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
367 367 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
368 368 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
369 369 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
370 370 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
371 371 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
372 372
373 373 @property
374 374 def action_as_day(self):
375 375 return datetime.date(*self.action_date.timetuple()[:3])
376 376
377 377 user = relationship('User')
378 378 repository = relationship('Repository',cascade='')
379 379
380 380
381 381 class UsersGroup(Base, BaseModel):
382 382 __tablename__ = 'users_groups'
383 383 __table_args__ = {'extend_existing':True}
384 384
385 385 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
386 386 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
387 387 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
388 388
389 389 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
390 390
391 391 def __repr__(self):
392 392 return '<userGroup(%s)>' % (self.users_group_name)
393 393
394 394 @classmethod
395 395 def get_by_group_name(cls, group_name, cache=False,
396 396 case_insensitive=False):
397 397 if case_insensitive:
398 398 q = cls.query().filter(cls.users_group_name.ilike(group_name))
399 399 else:
400 400 q = cls.query().filter(cls.users_group_name == group_name)
401 401 if cache:
402 402 q = q.options(FromCache("sql_cache_short",
403 403 "get_user_%s" % group_name))
404 404 return q.scalar()
405 405
406 406 @classmethod
407 407 def get(cls, users_group_id, cache=False):
408 408 users_group = cls.query()
409 409 if cache:
410 410 users_group = users_group.options(FromCache("sql_cache_short",
411 411 "get_users_group_%s" % users_group_id))
412 412 return users_group.get(users_group_id)
413 413
414 414
415 415 class UsersGroupMember(Base, BaseModel):
416 416 __tablename__ = 'users_groups_members'
417 417 __table_args__ = {'extend_existing':True}
418 418
419 419 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
420 420 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
421 421 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
422 422
423 423 user = relationship('User', lazy='joined')
424 424 users_group = relationship('UsersGroup')
425 425
426 426 def __init__(self, gr_id='', u_id=''):
427 427 self.users_group_id = gr_id
428 428 self.user_id = u_id
429 429
430 430 @staticmethod
431 431 def add_user_to_group(group, user):
432 432 ugm = UsersGroupMember()
433 433 ugm.users_group = group
434 434 ugm.user = user
435 435 Session.add(ugm)
436 436 Session.commit()
437 437 return ugm
438 438
439 439
440 440 class Repository(Base, BaseModel):
441 441 __tablename__ = 'repositories'
442 442 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
443 443
444 444 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
445 445 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
446 446 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
447 447 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
448 448 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
449 449 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
450 450 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
451 451 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
452 452 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
453 453 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
454 454
455 455 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
456 456 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
457 457
458 458 user = relationship('User')
459 459 fork = relationship('Repository', remote_side=repo_id)
460 460 group = relationship('RepoGroup')
461 461 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
462 462 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
463 463 stats = relationship('Statistics', cascade='all', uselist=False)
464 464
465 465 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
466 466
467 467 logs = relationship('UserLog')
468 468
469 469 def __repr__(self):
470 470 return "<%s('%s:%s')>" % (self.__class__.__name__,
471 471 self.repo_id, self.repo_name)
472 472
473 473 @classmethod
474 474 def url_sep(cls):
475 475 return '/'
476 476
477 477 @classmethod
478 478 def get_by_repo_name(cls, repo_name):
479 479 q = Session.query(cls).filter(cls.repo_name == repo_name)
480 480 q = q.options(joinedload(Repository.fork))\
481 481 .options(joinedload(Repository.user))\
482 482 .options(joinedload(Repository.group))
483 483 return q.scalar()
484 484
485 485 @classmethod
486 486 def get_repo_forks(cls, repo_id):
487 487 return cls.query().filter(Repository.fork_id == repo_id)
488 488
489 489 @classmethod
490 490 def base_path(cls):
491 491 """
492 492 Returns base path when all repos are stored
493 493
494 494 :param cls:
495 495 """
496 496 q = Session.query(RhodeCodeUi)\
497 497 .filter(RhodeCodeUi.ui_key == cls.url_sep())
498 498 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
499 499 return q.one().ui_value
500 500
501 501 @property
502 502 def just_name(self):
503 503 return self.repo_name.split(Repository.url_sep())[-1]
504 504
505 505 @property
506 506 def groups_with_parents(self):
507 507 groups = []
508 508 if self.group is None:
509 509 return groups
510 510
511 511 cur_gr = self.group
512 512 groups.insert(0, cur_gr)
513 513 while 1:
514 514 gr = getattr(cur_gr, 'parent_group', None)
515 515 cur_gr = cur_gr.parent_group
516 516 if gr is None:
517 517 break
518 518 groups.insert(0, gr)
519 519
520 520 return groups
521 521
522 522 @property
523 523 def groups_and_repo(self):
524 524 return self.groups_with_parents, self.just_name
525 525
526 526 @LazyProperty
527 527 def repo_path(self):
528 528 """
529 529 Returns base full path for that repository means where it actually
530 530 exists on a filesystem
531 531 """
532 532 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
533 533 Repository.url_sep())
534 534 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
535 535 return q.one().ui_value
536 536
537 537 @property
538 538 def repo_full_path(self):
539 539 p = [self.repo_path]
540 540 # we need to split the name by / since this is how we store the
541 541 # names in the database, but that eventually needs to be converted
542 542 # into a valid system path
543 543 p += self.repo_name.split(Repository.url_sep())
544 544 return os.path.join(*p)
545 545
546 546 def get_new_name(self, repo_name):
547 547 """
548 548 returns new full repository name based on assigned group and new new
549 549
550 550 :param group_name:
551 551 """
552 552 path_prefix = self.group.full_path_splitted if self.group else []
553 553 return Repository.url_sep().join(path_prefix + [repo_name])
554 554
555 555 @property
556 556 def _ui(self):
557 557 """
558 558 Creates an db based ui object for this repository
559 559 """
560 560 from mercurial import ui
561 561 from mercurial import config
562 562 baseui = ui.ui()
563 563
564 564 #clean the baseui object
565 565 baseui._ocfg = config.config()
566 566 baseui._ucfg = config.config()
567 567 baseui._tcfg = config.config()
568 568
569 569 ret = RhodeCodeUi.query()\
570 570 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
571 571
572 572 hg_ui = ret
573 573 for ui_ in hg_ui:
574 574 if ui_.ui_active:
575 575 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
576 576 ui_.ui_key, ui_.ui_value)
577 577 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
578 578
579 579 return baseui
580 580
581 581 @classmethod
582 582 def is_valid(cls, repo_name):
583 583 """
584 584 returns True if given repo name is a valid filesystem repository
585 585
586 @param cls:
587 @param repo_name:
586 :param cls:
587 :param repo_name:
588 588 """
589 589 from rhodecode.lib.utils import is_valid_repo
590 590
591 591 return is_valid_repo(repo_name, cls.base_path())
592 592
593 593 #==========================================================================
594 594 # SCM PROPERTIES
595 595 #==========================================================================
596 596
597 597 def get_changeset(self, rev):
598 598 return get_changeset_safe(self.scm_instance, rev)
599 599
600 600 @property
601 601 def tip(self):
602 602 return self.get_changeset('tip')
603 603
604 604 @property
605 605 def author(self):
606 606 return self.tip.author
607 607
608 608 @property
609 609 def last_change(self):
610 610 return self.scm_instance.last_change
611 611
612 612 #==========================================================================
613 613 # SCM CACHE INSTANCE
614 614 #==========================================================================
615 615
616 616 @property
617 617 def invalidate(self):
618 618 return CacheInvalidation.invalidate(self.repo_name)
619 619
620 620 def set_invalidate(self):
621 621 """
622 622 set a cache for invalidation for this instance
623 623 """
624 624 CacheInvalidation.set_invalidate(self.repo_name)
625 625
626 626 @LazyProperty
627 627 def scm_instance(self):
628 628 return self.__get_instance()
629 629
630 630 @property
631 631 def scm_instance_cached(self):
632 632 @cache_region('long_term')
633 633 def _c(repo_name):
634 634 return self.__get_instance()
635 635 rn = self.repo_name
636 636 log.debug('Getting cached instance of repo')
637 637 inv = self.invalidate
638 638 if inv is not None:
639 639 region_invalidate(_c, None, rn)
640 640 # update our cache
641 641 CacheInvalidation.set_valid(inv.cache_key)
642 642 return _c(rn)
643 643
644 644 def __get_instance(self):
645 645 repo_full_path = self.repo_full_path
646 646 try:
647 647 alias = get_scm(repo_full_path)[0]
648 648 log.debug('Creating instance of %s repository', alias)
649 649 backend = get_backend(alias)
650 650 except VCSError:
651 651 log.error(traceback.format_exc())
652 652 log.error('Perhaps this repository is in db and not in '
653 653 'filesystem run rescan repositories with '
654 654 '"destroy old data " option from admin panel')
655 655 return
656 656
657 657 if alias == 'hg':
658 658 repo = backend(safe_str(repo_full_path), create=False,
659 659 baseui=self._ui)
660 660 # skip hidden web repository
661 661 if repo._get_hidden():
662 662 return
663 663 else:
664 664 repo = backend(repo_full_path, create=False)
665 665
666 666 return repo
667 667
668 668
669 669 class RepoGroup(Base, BaseModel):
670 670 __tablename__ = 'groups'
671 671 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
672 672 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
673 673 __mapper_args__ = {'order_by':'group_name'}
674 674
675 675 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
676 676 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
677 677 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
678 678 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
679 679
680 680 parent_group = relationship('RepoGroup', remote_side=group_id)
681 681
682 682 def __init__(self, group_name='', parent_group=None):
683 683 self.group_name = group_name
684 684 self.parent_group = parent_group
685 685
686 686 def __repr__(self):
687 687 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
688 688 self.group_name)
689 689
690 690 @classmethod
691 691 def groups_choices(cls):
692 692 from webhelpers.html import literal as _literal
693 693 repo_groups = [('', '')]
694 694 sep = ' &raquo; '
695 695 _name = lambda k: _literal(sep.join(k))
696 696
697 697 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
698 698 for x in cls.query().all()])
699 699
700 700 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
701 701 return repo_groups
702 702
703 703 @classmethod
704 704 def url_sep(cls):
705 705 return '/'
706 706
707 707 @classmethod
708 708 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
709 709 if case_insensitive:
710 710 gr = cls.query()\
711 711 .filter(cls.group_name.ilike(group_name))
712 712 else:
713 713 gr = cls.query()\
714 714 .filter(cls.group_name == group_name)
715 715 if cache:
716 716 gr = gr.options(FromCache("sql_cache_short",
717 717 "get_group_%s" % group_name))
718 718 return gr.scalar()
719 719
720 720 @property
721 721 def parents(self):
722 722 parents_recursion_limit = 5
723 723 groups = []
724 724 if self.parent_group is None:
725 725 return groups
726 726 cur_gr = self.parent_group
727 727 groups.insert(0, cur_gr)
728 728 cnt = 0
729 729 while 1:
730 730 cnt += 1
731 731 gr = getattr(cur_gr, 'parent_group', None)
732 732 cur_gr = cur_gr.parent_group
733 733 if gr is None:
734 734 break
735 735 if cnt == parents_recursion_limit:
736 736 # this will prevent accidental infinit loops
737 737 log.error('group nested more than %s' %
738 738 parents_recursion_limit)
739 739 break
740 740
741 741 groups.insert(0, gr)
742 742 return groups
743 743
744 744 @property
745 745 def children(self):
746 746 return RepoGroup.query().filter(RepoGroup.parent_group == self)
747 747
748 748 @property
749 749 def name(self):
750 750 return self.group_name.split(RepoGroup.url_sep())[-1]
751 751
752 752 @property
753 753 def full_path(self):
754 754 return self.group_name
755 755
756 756 @property
757 757 def full_path_splitted(self):
758 758 return self.group_name.split(RepoGroup.url_sep())
759 759
760 760 @property
761 761 def repositories(self):
762 762 return Repository.query().filter(Repository.group == self)
763 763
764 764 @property
765 765 def repositories_recursive_count(self):
766 766 cnt = self.repositories.count()
767 767
768 768 def children_count(group):
769 769 cnt = 0
770 770 for child in group.children:
771 771 cnt += child.repositories.count()
772 772 cnt += children_count(child)
773 773 return cnt
774 774
775 775 return cnt + children_count(self)
776 776
777 777
778 778 def get_new_name(self, group_name):
779 779 """
780 780 returns new full group name based on parent and new name
781 781
782 782 :param group_name:
783 783 """
784 784 path_prefix = (self.parent_group.full_path_splitted if
785 785 self.parent_group else [])
786 786 return RepoGroup.url_sep().join(path_prefix + [group_name])
787 787
788 788
789 789 class Permission(Base, BaseModel):
790 790 __tablename__ = 'permissions'
791 791 __table_args__ = {'extend_existing':True}
792 792 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
793 793 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
794 794 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
795 795
796 796 def __repr__(self):
797 797 return "<%s('%s:%s')>" % (self.__class__.__name__,
798 798 self.permission_id, self.permission_name)
799 799
800 800 @classmethod
801 801 def get_by_key(cls, key):
802 802 return cls.query().filter(cls.permission_name == key).scalar()
803 803
804 804 @classmethod
805 805 def get_default_perms(cls, default_user_id):
806 806 q = Session.query(UserRepoToPerm, Repository, cls)\
807 807 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
808 808 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
809 809 .filter(UserRepoToPerm.user_id == default_user_id)
810 810
811 811 return q.all()
812 812
813 813
814 814 class UserRepoToPerm(Base, BaseModel):
815 815 __tablename__ = 'repo_to_perm'
816 816 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
817 817 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
818 818 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
819 819 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
820 820 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
821 821
822 822 user = relationship('User')
823 823 permission = relationship('Permission')
824 824 repository = relationship('Repository')
825 825
826 826 @classmethod
827 827 def create(cls, user, repository, permission):
828 828 n = cls()
829 829 n.user = user
830 830 n.repository = repository
831 831 n.permission = permission
832 832 Session.add(n)
833 833 return n
834 834
835 835 def __repr__(self):
836 836 return '<user:%s => %s >' % (self.user, self.repository)
837 837
838 838
839 839 class UserToPerm(Base, BaseModel):
840 840 __tablename__ = 'user_to_perm'
841 841 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
842 842 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
843 843 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
844 844 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
845 845
846 846 user = relationship('User')
847 847 permission = relationship('Permission', lazy='joined')
848 848
849 849
850 850 class UsersGroupRepoToPerm(Base, BaseModel):
851 851 __tablename__ = 'users_group_repo_to_perm'
852 852 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
853 853 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
854 854 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
855 855 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
856 856 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
857 857
858 858 users_group = relationship('UsersGroup')
859 859 permission = relationship('Permission')
860 860 repository = relationship('Repository')
861 861
862 862 @classmethod
863 863 def create(cls, users_group, repository, permission):
864 864 n = cls()
865 865 n.users_group = users_group
866 866 n.repository = repository
867 867 n.permission = permission
868 868 Session.add(n)
869 869 return n
870 870
871 871 def __repr__(self):
872 872 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
873 873
874 874
875 875 class UsersGroupToPerm(Base, BaseModel):
876 876 __tablename__ = 'users_group_to_perm'
877 877 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
878 878 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
879 879 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
880 880
881 881 users_group = relationship('UsersGroup')
882 882 permission = relationship('Permission')
883 883
884 884
885 885 class UserRepoGroupToPerm(Base, BaseModel):
886 886 __tablename__ = 'group_to_perm'
887 887 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
888 888
889 889 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
890 890 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
891 891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
892 892 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
893 893
894 894 user = relationship('User')
895 895 permission = relationship('Permission')
896 896 group = relationship('RepoGroup')
897 897
898 898
899 899 class UsersGroupRepoGroupToPerm(Base, BaseModel):
900 900 __tablename__ = 'users_group_repo_group_to_perm'
901 901 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
902 902
903 903 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)
904 904 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
905 905 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
906 906 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
907 907
908 908 users_group = relationship('UsersGroup')
909 909 permission = relationship('Permission')
910 910 group = relationship('RepoGroup')
911 911
912 912
913 913 class Statistics(Base, BaseModel):
914 914 __tablename__ = 'statistics'
915 915 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
916 916 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
917 917 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
918 918 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
919 919 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
920 920 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
921 921 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
922 922
923 923 repository = relationship('Repository', single_parent=True)
924 924
925 925
926 926 class UserFollowing(Base, BaseModel):
927 927 __tablename__ = 'user_followings'
928 928 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
929 929 UniqueConstraint('user_id', 'follows_user_id')
930 930 , {'extend_existing':True})
931 931
932 932 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
933 933 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
934 934 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
935 935 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
936 936 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
937 937
938 938 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
939 939
940 940 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
941 941 follows_repository = relationship('Repository', order_by='Repository.repo_name')
942 942
943 943
944 944 @classmethod
945 945 def get_repo_followers(cls, repo_id):
946 946 return cls.query().filter(cls.follows_repo_id == repo_id)
947 947
948 948
949 949 class CacheInvalidation(Base, BaseModel):
950 950 __tablename__ = 'cache_invalidation'
951 951 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
952 952 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
953 953 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
954 954 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
955 955 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
956 956
957 957
958 958 def __init__(self, cache_key, cache_args=''):
959 959 self.cache_key = cache_key
960 960 self.cache_args = cache_args
961 961 self.cache_active = False
962 962
963 963 def __repr__(self):
964 964 return "<%s('%s:%s')>" % (self.__class__.__name__,
965 965 self.cache_id, self.cache_key)
966 966
967 967 @classmethod
968 968 def invalidate(cls, key):
969 969 """
970 970 Returns Invalidation object if this given key should be invalidated
971 971 None otherwise. `cache_active = False` means that this cache
972 972 state is not valid and needs to be invalidated
973 973
974 974 :param key:
975 975 """
976 976 return cls.query()\
977 977 .filter(CacheInvalidation.cache_key == key)\
978 978 .filter(CacheInvalidation.cache_active == False)\
979 979 .scalar()
980 980
981 981 @classmethod
982 982 def set_invalidate(cls, key):
983 983 """
984 984 Mark this Cache key for invalidation
985 985
986 986 :param key:
987 987 """
988 988
989 989 log.debug('marking %s for invalidation' % key)
990 990 inv_obj = Session.query(cls)\
991 991 .filter(cls.cache_key == key).scalar()
992 992 if inv_obj:
993 993 inv_obj.cache_active = False
994 994 else:
995 995 log.debug('cache key not found in invalidation db -> creating one')
996 996 inv_obj = CacheInvalidation(key)
997 997
998 998 try:
999 999 Session.add(inv_obj)
1000 1000 Session.commit()
1001 1001 except Exception:
1002 1002 log.error(traceback.format_exc())
1003 1003 Session.rollback()
1004 1004
1005 1005 @classmethod
1006 1006 def set_valid(cls, key):
1007 1007 """
1008 1008 Mark this cache key as active and currently cached
1009 1009
1010 1010 :param key:
1011 1011 """
1012 1012 inv_obj = CacheInvalidation.query()\
1013 1013 .filter(CacheInvalidation.cache_key == key).scalar()
1014 1014 inv_obj.cache_active = True
1015 1015 Session.add(inv_obj)
1016 1016 Session.commit()
1017 1017
1018 1018
1019 1019 class ChangesetComment(Base, BaseModel):
1020 1020 __tablename__ = 'changeset_comments'
1021 1021 __table_args__ = ({'extend_existing':True},)
1022 1022 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1023 1023 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1024 1024 revision = Column('revision', String(40), nullable=False)
1025 1025 line_no = Column('line_no', Unicode(10), nullable=True)
1026 1026 f_path = Column('f_path', Unicode(1000), nullable=True)
1027 1027 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1028 1028 text = Column('text', Unicode(25000), nullable=False)
1029 1029 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1030 1030
1031 1031 author = relationship('User', lazy='joined')
1032 1032 repo = relationship('Repository')
1033 1033
1034 1034
1035 1035 @classmethod
1036 1036 def get_users(cls, revision):
1037 1037 """
1038 1038 Returns user associated with this changesetComment. ie those
1039 1039 who actually commented
1040 1040
1041 1041 :param cls:
1042 1042 :param revision:
1043 1043 """
1044 1044 return Session.query(User)\
1045 1045 .filter(cls.revision == revision)\
1046 1046 .join(ChangesetComment.author).all()
1047 1047
1048 1048
1049 1049 class Notification(Base, BaseModel):
1050 1050 __tablename__ = 'notifications'
1051 1051 __table_args__ = ({'extend_existing':True})
1052 1052
1053 1053 TYPE_CHANGESET_COMMENT = u'cs_comment'
1054 1054 TYPE_MESSAGE = u'message'
1055 1055 TYPE_MENTION = u'mention'
1056 1056 TYPE_REGISTRATION = u'registration'
1057 1057
1058 1058 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1059 1059 subject = Column('subject', Unicode(512), nullable=True)
1060 1060 body = Column('body', Unicode(50000), nullable=True)
1061 1061 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1062 1062 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1063 1063 type_ = Column('type', Unicode(256))
1064 1064
1065 1065 created_by_user = relationship('User')
1066 1066 notifications_to_users = relationship('UserNotification', lazy='joined',
1067 1067 cascade="all, delete, delete-orphan")
1068 1068
1069 1069 @property
1070 1070 def recipients(self):
1071 1071 return [x.user for x in UserNotification.query()\
1072 1072 .filter(UserNotification.notification == self).all()]
1073 1073
1074 1074 @classmethod
1075 1075 def create(cls, created_by, subject, body, recipients, type_=None):
1076 1076 if type_ is None:
1077 1077 type_ = Notification.TYPE_MESSAGE
1078 1078
1079 1079 notification = cls()
1080 1080 notification.created_by_user = created_by
1081 1081 notification.subject = subject
1082 1082 notification.body = body
1083 1083 notification.type_ = type_
1084 1084 notification.created_on = datetime.datetime.now()
1085 1085
1086 1086 for u in recipients:
1087 1087 assoc = UserNotification()
1088 1088 assoc.notification = notification
1089 1089 u.notifications.append(assoc)
1090 1090 Session.add(notification)
1091 1091 return notification
1092 1092
1093 1093 @property
1094 1094 def description(self):
1095 1095 from rhodecode.model.notification import NotificationModel
1096 1096 return NotificationModel().make_description(self)
1097 1097
1098 1098
1099 1099 class UserNotification(Base, BaseModel):
1100 1100 __tablename__ = 'user_to_notification'
1101 1101 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1102 1102 {'extend_existing':True})
1103 1103 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1104 1104 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1105 1105 read = Column('read', Boolean, default=False)
1106 1106 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1107 1107
1108 1108 user = relationship('User', lazy="joined")
1109 1109 notification = relationship('Notification', lazy="joined",
1110 1110 order_by=lambda:Notification.created_on.desc(),)
1111 1111
1112 1112 def mark_as_read(self):
1113 1113 self.read = True
1114 1114 Session.add(self)
1115 1115
1116 1116
1117 1117 class DbMigrateVersion(Base, BaseModel):
1118 1118 __tablename__ = 'db_migrate_version'
1119 1119 __table_args__ = {'extend_existing':True}
1120 1120 repository_id = Column('repository_id', String(250), primary_key=True)
1121 1121 repository_path = Column('repository_path', Text)
1122 1122 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now