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