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