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