Mercurial > kallithea
comparison rhodecode/lib/dbmigrate/schema/db_1_5_0.py @ 3148:b31984972e95 beta
Migration upgrades cache for lightweight dashboard
Fixed some migration issues
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sat, 05 Jan 2013 02:20:35 +0100 |
parents | d3200c58764e |
children | fa6ba6727475 |
comparison
equal
deleted
inserted
replaced
3147:8182ebed2922 | 3148:b31984972e95 |
---|---|
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 """ | 2 """ |
3 rhodecode.model.db_1_4_0 | 3 rhodecode.model.db_1_5_0 |
4 ~~~~~~~~~~~~~~~~~~~~~~~~ | 4 ~~~~~~~~~~~~~~~~~~~~~~~~ |
5 | 5 |
6 Database Models for RhodeCode <=1.5.X | 6 Database Models for RhodeCode <=1.5.2 |
7 | 7 |
8 :created_on: Apr 08, 2010 | 8 :created_on: Apr 08, 2010 |
9 :author: marcink | 9 :author: marcink |
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> | 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com> |
11 :license: GPLv3, see COPYING for more details. | 11 :license: GPLv3, see COPYING for more details. |
21 # GNU General Public License for more details. | 21 # GNU General Public License for more details. |
22 # | 22 # |
23 # You should have received a copy of the GNU General Public License | 23 # You should have received a copy of the GNU General Public License |
24 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 24 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
25 | 25 |
26 #TODO: replace that will db.py content after 1.6 Release | 26 import os |
27 | 27 import logging |
28 from rhodecode.model.db import * | 28 import datetime |
29 import traceback | |
30 import hashlib | |
31 import time | |
32 from collections import defaultdict | |
33 | |
34 from sqlalchemy import * | |
35 from sqlalchemy.ext.hybrid import hybrid_property | |
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates | |
37 from sqlalchemy.exc import DatabaseError | |
38 from beaker.cache import cache_region, region_invalidate | |
39 from webob.exc import HTTPNotFound | |
40 | |
41 from pylons.i18n.translation import lazy_ugettext as _ | |
42 | |
43 from rhodecode.lib.vcs import get_backend | |
44 from rhodecode.lib.vcs.utils.helpers import get_scm | |
45 from rhodecode.lib.vcs.exceptions import VCSError | |
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty | |
47 | |
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \ | |
49 safe_unicode, remove_suffix, remove_prefix | |
50 from rhodecode.lib.compat import json | |
51 from rhodecode.lib.caching_query import FromCache | |
52 | |
53 from rhodecode.model.meta import Base, Session | |
54 | |
55 URL_SEP = '/' | |
56 log = logging.getLogger(__name__) | |
57 | |
58 #============================================================================== | |
59 # BASE CLASSES | |
60 #============================================================================== | |
61 | |
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() | |
63 | |
64 | |
65 class BaseModel(object): | |
66 """ | |
67 Base Model for all classess | |
68 """ | |
69 | |
70 @classmethod | |
71 def _get_keys(cls): | |
72 """return column names for this model """ | |
73 return class_mapper(cls).c.keys() | |
74 | |
75 def get_dict(self): | |
76 """ | |
77 return dict with keys and values corresponding | |
78 to this model data """ | |
79 | |
80 d = {} | |
81 for k in self._get_keys(): | |
82 d[k] = getattr(self, k) | |
83 | |
84 # also use __json__() if present to get additional fields | |
85 _json_attr = getattr(self, '__json__', None) | |
86 if _json_attr: | |
87 # update with attributes from __json__ | |
88 if callable(_json_attr): | |
89 _json_attr = _json_attr() | |
90 for k, val in _json_attr.iteritems(): | |
91 d[k] = val | |
92 return d | |
93 | |
94 def get_appstruct(self): | |
95 """return list with keys and values tupples corresponding | |
96 to this model data """ | |
97 | |
98 l = [] | |
99 for k in self._get_keys(): | |
100 l.append((k, getattr(self, k),)) | |
101 return l | |
102 | |
103 def populate_obj(self, populate_dict): | |
104 """populate model with data from given populate_dict""" | |
105 | |
106 for k in self._get_keys(): | |
107 if k in populate_dict: | |
108 setattr(self, k, populate_dict[k]) | |
109 | |
110 @classmethod | |
111 def query(cls): | |
112 return Session().query(cls) | |
113 | |
114 @classmethod | |
115 def get(cls, id_): | |
116 if id_: | |
117 return cls.query().get(id_) | |
118 | |
119 @classmethod | |
120 def get_or_404(cls, id_): | |
121 try: | |
122 id_ = int(id_) | |
123 except (TypeError, ValueError): | |
124 raise HTTPNotFound | |
125 | |
126 res = cls.query().get(id_) | |
127 if not res: | |
128 raise HTTPNotFound | |
129 return res | |
130 | |
131 @classmethod | |
132 def getAll(cls): | |
133 return cls.query().all() | |
134 | |
135 @classmethod | |
136 def delete(cls, id_): | |
137 obj = cls.query().get(id_) | |
138 Session().delete(obj) | |
139 | |
140 def __repr__(self): | |
141 if hasattr(self, '__unicode__'): | |
142 # python repr needs to return str | |
143 return safe_str(self.__unicode__()) | |
144 return '<DB:%s>' % (self.__class__.__name__) | |
145 | |
146 | |
147 class RhodeCodeSetting(Base, BaseModel): | |
148 __tablename__ = 'rhodecode_settings' | |
149 __table_args__ = ( | |
150 UniqueConstraint('app_settings_name'), | |
151 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
152 'mysql_charset': 'utf8'} | |
153 ) | |
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
157 | |
158 def __init__(self, k='', v=''): | |
159 self.app_settings_name = k | |
160 self.app_settings_value = v | |
161 | |
162 @validates('_app_settings_value') | |
163 def validate_settings_value(self, key, val): | |
164 assert type(val) == unicode | |
165 return val | |
166 | |
167 @hybrid_property | |
168 def app_settings_value(self): | |
169 v = self._app_settings_value | |
170 if self.app_settings_name in ["ldap_active", | |
171 "default_repo_enable_statistics", | |
172 "default_repo_enable_locking", | |
173 "default_repo_private", | |
174 "default_repo_enable_downloads"]: | |
175 v = str2bool(v) | |
176 return v | |
177 | |
178 @app_settings_value.setter | |
179 def app_settings_value(self, val): | |
180 """ | |
181 Setter that will always make sure we use unicode in app_settings_value | |
182 | |
183 :param val: | |
184 """ | |
185 self._app_settings_value = safe_unicode(val) | |
186 | |
187 def __unicode__(self): | |
188 return u"<%s('%s:%s')>" % ( | |
189 self.__class__.__name__, | |
190 self.app_settings_name, self.app_settings_value | |
191 ) | |
192 | |
193 @classmethod | |
194 def get_by_name(cls, key): | |
195 return cls.query()\ | |
196 .filter(cls.app_settings_name == key).scalar() | |
197 | |
198 @classmethod | |
199 def get_by_name_or_create(cls, key): | |
200 res = cls.get_by_name(key) | |
201 if not res: | |
202 res = cls(key) | |
203 return res | |
204 | |
205 @classmethod | |
206 def get_app_settings(cls, cache=False): | |
207 | |
208 ret = cls.query() | |
209 | |
210 if cache: | |
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) | |
212 | |
213 if not ret: | |
214 raise Exception('Could not get application settings !') | |
215 settings = {} | |
216 for each in ret: | |
217 settings['rhodecode_' + each.app_settings_name] = \ | |
218 each.app_settings_value | |
219 | |
220 return settings | |
221 | |
222 @classmethod | |
223 def get_ldap_settings(cls, cache=False): | |
224 ret = cls.query()\ | |
225 .filter(cls.app_settings_name.startswith('ldap_')).all() | |
226 fd = {} | |
227 for row in ret: | |
228 fd.update({row.app_settings_name: row.app_settings_value}) | |
229 | |
230 return fd | |
231 | |
232 @classmethod | |
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False): | |
234 ret = cls.query()\ | |
235 .filter(cls.app_settings_name.startswith('default_')).all() | |
236 fd = {} | |
237 for row in ret: | |
238 key = row.app_settings_name | |
239 if strip_prefix: | |
240 key = remove_prefix(key, prefix='default_') | |
241 fd.update({key: row.app_settings_value}) | |
242 | |
243 return fd | |
244 | |
245 | |
246 class RhodeCodeUi(Base, BaseModel): | |
247 __tablename__ = 'rhodecode_ui' | |
248 __table_args__ = ( | |
249 UniqueConstraint('ui_key'), | |
250 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
251 'mysql_charset': 'utf8'} | |
252 ) | |
253 | |
254 HOOK_UPDATE = 'changegroup.update' | |
255 HOOK_REPO_SIZE = 'changegroup.repo_size' | |
256 HOOK_PUSH = 'changegroup.push_logger' | |
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push' | |
258 HOOK_PULL = 'outgoing.pull_logger' | |
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull' | |
260 | |
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) | |
266 | |
267 @classmethod | |
268 def get_by_key(cls, key): | |
269 return cls.query().filter(cls.ui_key == key).scalar() | |
270 | |
271 @classmethod | |
272 def get_builtin_hooks(cls): | |
273 q = cls.query() | |
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, | |
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, | |
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL])) | |
277 return q.all() | |
278 | |
279 @classmethod | |
280 def get_custom_hooks(cls): | |
281 q = cls.query() | |
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, | |
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, | |
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL])) | |
285 q = q.filter(cls.ui_section == 'hooks') | |
286 return q.all() | |
287 | |
288 @classmethod | |
289 def get_repos_location(cls): | |
290 return cls.get_by_key('/').ui_value | |
291 | |
292 @classmethod | |
293 def create_or_update_hook(cls, key, val): | |
294 new_ui = cls.get_by_key(key) or cls() | |
295 new_ui.ui_section = 'hooks' | |
296 new_ui.ui_active = True | |
297 new_ui.ui_key = key | |
298 new_ui.ui_value = val | |
299 | |
300 Session().add(new_ui) | |
301 | |
302 def __repr__(self): | |
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key, | |
304 self.ui_value) | |
305 | |
306 | |
307 class User(Base, BaseModel): | |
308 __tablename__ = 'users' | |
309 __table_args__ = ( | |
310 UniqueConstraint('username'), UniqueConstraint('email'), | |
311 Index('u_username_idx', 'username'), | |
312 Index('u_email_idx', 'email'), | |
313 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
314 'mysql_charset': 'utf8'} | |
315 ) | |
316 DEFAULT_USER = 'default' | |
317 DEFAULT_PERMISSIONS = [ | |
318 'hg.register.manual_activate', 'hg.create.repository', | |
319 'hg.fork.repository', 'repository.read', 'group.read' | |
320 ] | |
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True) | |
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) | |
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) | |
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) | |
333 | |
334 user_log = relationship('UserLog') | |
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') | |
336 | |
337 repositories = relationship('Repository') | |
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all') | |
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all') | |
340 | |
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all') | |
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all') | |
343 | |
344 group_member = relationship('UsersGroupMember', cascade='all') | |
345 | |
346 notifications = relationship('UserNotification', cascade='all') | |
347 # notifications assigned to this user | |
348 user_created_notifications = relationship('Notification', cascade='all') | |
349 # comments created by this user | |
350 user_comments = relationship('ChangesetComment', cascade='all') | |
351 #extra emails for this user | |
352 user_emails = relationship('UserEmailMap', cascade='all') | |
353 | |
354 @hybrid_property | |
355 def email(self): | |
356 return self._email | |
357 | |
358 @email.setter | |
359 def email(self, val): | |
360 self._email = val.lower() if val else None | |
361 | |
362 @property | |
363 def firstname(self): | |
364 # alias for future | |
365 return self.name | |
366 | |
367 @property | |
368 def emails(self): | |
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all() | |
370 return [self.email] + [x.email for x in other] | |
371 | |
372 @property | |
373 def username_and_name(self): | |
374 return '%s (%s %s)' % (self.username, self.firstname, self.lastname) | |
375 | |
376 @property | |
377 def full_name(self): | |
378 return '%s %s' % (self.firstname, self.lastname) | |
379 | |
380 @property | |
381 def full_name_or_username(self): | |
382 return ('%s %s' % (self.firstname, self.lastname) | |
383 if (self.firstname and self.lastname) else self.username) | |
384 | |
385 @property | |
386 def full_contact(self): | |
387 return '%s %s <%s>' % (self.firstname, self.lastname, self.email) | |
388 | |
389 @property | |
390 def short_contact(self): | |
391 return '%s %s' % (self.firstname, self.lastname) | |
392 | |
393 @property | |
394 def is_admin(self): | |
395 return self.admin | |
396 | |
397 def __unicode__(self): | |
398 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, | |
399 self.user_id, self.username) | |
400 | |
401 @classmethod | |
402 def get_by_username(cls, username, case_insensitive=False, cache=False): | |
403 if case_insensitive: | |
404 q = cls.query().filter(cls.username.ilike(username)) | |
405 else: | |
406 q = cls.query().filter(cls.username == username) | |
407 | |
408 if cache: | |
409 q = q.options(FromCache( | |
410 "sql_cache_short", | |
411 "get_user_%s" % _hash_key(username) | |
412 ) | |
413 ) | |
414 return q.scalar() | |
415 | |
416 @classmethod | |
417 def get_by_api_key(cls, api_key, cache=False): | |
418 q = cls.query().filter(cls.api_key == api_key) | |
419 | |
420 if cache: | |
421 q = q.options(FromCache("sql_cache_short", | |
422 "get_api_key_%s" % api_key)) | |
423 return q.scalar() | |
424 | |
425 @classmethod | |
426 def get_by_email(cls, email, case_insensitive=False, cache=False): | |
427 if case_insensitive: | |
428 q = cls.query().filter(cls.email.ilike(email)) | |
429 else: | |
430 q = cls.query().filter(cls.email == email) | |
431 | |
432 if cache: | |
433 q = q.options(FromCache("sql_cache_short", | |
434 "get_email_key_%s" % email)) | |
435 | |
436 ret = q.scalar() | |
437 if ret is None: | |
438 q = UserEmailMap.query() | |
439 # try fetching in alternate email map | |
440 if case_insensitive: | |
441 q = q.filter(UserEmailMap.email.ilike(email)) | |
442 else: | |
443 q = q.filter(UserEmailMap.email == email) | |
444 q = q.options(joinedload(UserEmailMap.user)) | |
445 if cache: | |
446 q = q.options(FromCache("sql_cache_short", | |
447 "get_email_map_key_%s" % email)) | |
448 ret = getattr(q.scalar(), 'user', None) | |
449 | |
450 return ret | |
451 | |
452 def update_lastlogin(self): | |
453 """Update user lastlogin""" | |
454 self.last_login = datetime.datetime.now() | |
455 Session().add(self) | |
456 log.debug('updated user %s lastlogin' % self.username) | |
457 | |
458 def get_api_data(self): | |
459 """ | |
460 Common function for generating user related data for API | |
461 """ | |
462 user = self | |
463 data = dict( | |
464 user_id=user.user_id, | |
465 username=user.username, | |
466 firstname=user.name, | |
467 lastname=user.lastname, | |
468 email=user.email, | |
469 emails=user.emails, | |
470 api_key=user.api_key, | |
471 active=user.active, | |
472 admin=user.admin, | |
473 ldap_dn=user.ldap_dn, | |
474 last_login=user.last_login, | |
475 ) | |
476 return data | |
477 | |
478 def __json__(self): | |
479 data = dict( | |
480 full_name=self.full_name, | |
481 full_name_or_username=self.full_name_or_username, | |
482 short_contact=self.short_contact, | |
483 full_contact=self.full_contact | |
484 ) | |
485 data.update(self.get_api_data()) | |
486 return data | |
487 | |
488 | |
489 class UserEmailMap(Base, BaseModel): | |
490 __tablename__ = 'user_email_map' | |
491 __table_args__ = ( | |
492 Index('uem_email_idx', 'email'), | |
493 UniqueConstraint('email'), | |
494 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
495 'mysql_charset': 'utf8'} | |
496 ) | |
497 __mapper_args__ = {} | |
498 | |
499 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
500 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |
501 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
502 user = relationship('User', lazy='joined') | |
503 | |
504 @validates('_email') | |
505 def validate_email(self, key, email): | |
506 # check if this email is not main one | |
507 main_email = Session().query(User).filter(User.email == email).scalar() | |
508 if main_email is not None: | |
509 raise AttributeError('email %s is present is user table' % email) | |
510 return email | |
511 | |
512 @hybrid_property | |
513 def email(self): | |
514 return self._email | |
515 | |
516 @email.setter | |
517 def email(self, val): | |
518 self._email = val.lower() if val else None | |
519 | |
520 | |
521 class UserLog(Base, BaseModel): | |
522 __tablename__ = 'user_logs' | |
523 __table_args__ = ( | |
524 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
525 'mysql_charset': 'utf8'}, | |
526 ) | |
527 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
528 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |
529 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
530 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) | |
531 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
532 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
533 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
534 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None) | |
535 | |
536 @property | |
537 def action_as_day(self): | |
538 return datetime.date(*self.action_date.timetuple()[:3]) | |
539 | |
540 user = relationship('User') | |
541 repository = relationship('Repository', cascade='') | |
542 | |
543 | |
544 class UsersGroup(Base, BaseModel): | |
545 __tablename__ = 'users_groups' | |
546 __table_args__ = ( | |
547 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
548 'mysql_charset': 'utf8'}, | |
549 ) | |
550 | |
551 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
552 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |
553 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None) | |
554 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True) | |
555 | |
556 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined") | |
557 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all') | |
558 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') | |
559 | |
560 def __unicode__(self): | |
561 return u'<userGroup(%s)>' % (self.users_group_name) | |
562 | |
563 @classmethod | |
564 def get_by_group_name(cls, group_name, cache=False, | |
565 case_insensitive=False): | |
566 if case_insensitive: | |
567 q = cls.query().filter(cls.users_group_name.ilike(group_name)) | |
568 else: | |
569 q = cls.query().filter(cls.users_group_name == group_name) | |
570 if cache: | |
571 q = q.options(FromCache( | |
572 "sql_cache_short", | |
573 "get_user_%s" % _hash_key(group_name) | |
574 ) | |
575 ) | |
576 return q.scalar() | |
577 | |
578 @classmethod | |
579 def get(cls, users_group_id, cache=False): | |
580 users_group = cls.query() | |
581 if cache: | |
582 users_group = users_group.options(FromCache("sql_cache_short", | |
583 "get_users_group_%s" % users_group_id)) | |
584 return users_group.get(users_group_id) | |
585 | |
586 def get_api_data(self): | |
587 users_group = self | |
588 | |
589 data = dict( | |
590 users_group_id=users_group.users_group_id, | |
591 group_name=users_group.users_group_name, | |
592 active=users_group.users_group_active, | |
593 ) | |
594 | |
595 return data | |
596 | |
597 | |
598 class UsersGroupMember(Base, BaseModel): | |
599 __tablename__ = 'users_groups_members' | |
600 __table_args__ = ( | |
601 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
602 'mysql_charset': 'utf8'}, | |
603 ) | |
604 | |
605 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
606 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
607 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
608 | |
609 user = relationship('User', lazy='joined') | |
610 users_group = relationship('UsersGroup') | |
611 | |
612 def __init__(self, gr_id='', u_id=''): | |
613 self.users_group_id = gr_id | |
614 self.user_id = u_id | |
615 | |
616 | |
617 class Repository(Base, BaseModel): | |
618 __tablename__ = 'repositories' | |
619 __table_args__ = ( | |
620 UniqueConstraint('repo_name'), | |
621 Index('r_repo_name_idx', 'repo_name'), | |
622 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
623 'mysql_charset': 'utf8'}, | |
624 ) | |
625 | |
626 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
627 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |
628 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
629 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |
630 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) | |
631 private = Column("private", Boolean(), nullable=True, unique=None, default=None) | |
632 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) | |
633 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) | |
634 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
635 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
636 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
637 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |
638 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |
639 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
640 | |
641 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) | |
642 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) | |
643 | |
644 user = relationship('User') | |
645 fork = relationship('Repository', remote_side=repo_id) | |
646 group = relationship('RepoGroup') | |
647 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') | |
648 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all') | |
649 stats = relationship('Statistics', cascade='all', uselist=False) | |
650 | |
651 followers = relationship('UserFollowing', | |
652 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', | |
653 cascade='all') | |
654 | |
655 logs = relationship('UserLog') | |
656 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") | |
657 | |
658 pull_requests_org = relationship('PullRequest', | |
659 primaryjoin='PullRequest.org_repo_id==Repository.repo_id', | |
660 cascade="all, delete, delete-orphan") | |
661 | |
662 pull_requests_other = relationship('PullRequest', | |
663 primaryjoin='PullRequest.other_repo_id==Repository.repo_id', | |
664 cascade="all, delete, delete-orphan") | |
665 | |
666 def __unicode__(self): | |
667 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, | |
668 self.repo_name) | |
669 | |
670 @hybrid_property | |
671 def locked(self): | |
672 # always should return [user_id, timelocked] | |
673 if self._locked: | |
674 _lock_info = self._locked.split(':') | |
675 return int(_lock_info[0]), _lock_info[1] | |
676 return [None, None] | |
677 | |
678 @locked.setter | |
679 def locked(self, val): | |
680 if val and isinstance(val, (list, tuple)): | |
681 self._locked = ':'.join(map(str, val)) | |
682 else: | |
683 self._locked = None | |
684 | |
685 @classmethod | |
686 def url_sep(cls): | |
687 return URL_SEP | |
688 | |
689 @classmethod | |
690 def get_by_repo_name(cls, repo_name): | |
691 q = Session().query(cls).filter(cls.repo_name == repo_name) | |
692 q = q.options(joinedload(Repository.fork))\ | |
693 .options(joinedload(Repository.user))\ | |
694 .options(joinedload(Repository.group)) | |
695 return q.scalar() | |
696 | |
697 @classmethod | |
698 def get_by_full_path(cls, repo_full_path): | |
699 repo_name = repo_full_path.split(cls.base_path(), 1)[-1] | |
700 return cls.get_by_repo_name(repo_name.strip(URL_SEP)) | |
701 | |
702 @classmethod | |
703 def get_repo_forks(cls, repo_id): | |
704 return cls.query().filter(Repository.fork_id == repo_id) | |
705 | |
706 @classmethod | |
707 def base_path(cls): | |
708 """ | |
709 Returns base path when all repos are stored | |
710 | |
711 :param cls: | |
712 """ | |
713 q = Session().query(RhodeCodeUi)\ | |
714 .filter(RhodeCodeUi.ui_key == cls.url_sep()) | |
715 q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |
716 return q.one().ui_value | |
717 | |
718 @property | |
719 def forks(self): | |
720 """ | |
721 Return forks of this repo | |
722 """ | |
723 return Repository.get_repo_forks(self.repo_id) | |
724 | |
725 @property | |
726 def parent(self): | |
727 """ | |
728 Returns fork parent | |
729 """ | |
730 return self.fork | |
731 | |
732 @property | |
733 def just_name(self): | |
734 return self.repo_name.split(Repository.url_sep())[-1] | |
735 | |
736 @property | |
737 def groups_with_parents(self): | |
738 groups = [] | |
739 if self.group is None: | |
740 return groups | |
741 | |
742 cur_gr = self.group | |
743 groups.insert(0, cur_gr) | |
744 while 1: | |
745 gr = getattr(cur_gr, 'parent_group', None) | |
746 cur_gr = cur_gr.parent_group | |
747 if gr is None: | |
748 break | |
749 groups.insert(0, gr) | |
750 | |
751 return groups | |
752 | |
753 @property | |
754 def groups_and_repo(self): | |
755 return self.groups_with_parents, self.just_name | |
756 | |
757 @LazyProperty | |
758 def repo_path(self): | |
759 """ | |
760 Returns base full path for that repository means where it actually | |
761 exists on a filesystem | |
762 """ | |
763 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == | |
764 Repository.url_sep()) | |
765 q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |
766 return q.one().ui_value | |
767 | |
768 @property | |
769 def repo_full_path(self): | |
770 p = [self.repo_path] | |
771 # we need to split the name by / since this is how we store the | |
772 # names in the database, but that eventually needs to be converted | |
773 # into a valid system path | |
774 p += self.repo_name.split(Repository.url_sep()) | |
775 return os.path.join(*p) | |
776 | |
777 @property | |
778 def cache_keys(self): | |
779 """ | |
780 Returns associated cache keys for that repo | |
781 """ | |
782 return CacheInvalidation.query()\ | |
783 .filter(CacheInvalidation.cache_args == self.repo_name)\ | |
784 .order_by(CacheInvalidation.cache_key)\ | |
785 .all() | |
786 | |
787 def get_new_name(self, repo_name): | |
788 """ | |
789 returns new full repository name based on assigned group and new new | |
790 | |
791 :param group_name: | |
792 """ | |
793 path_prefix = self.group.full_path_splitted if self.group else [] | |
794 return Repository.url_sep().join(path_prefix + [repo_name]) | |
795 | |
796 @property | |
797 def _ui(self): | |
798 """ | |
799 Creates an db based ui object for this repository | |
800 """ | |
801 from rhodecode.lib.utils import make_ui | |
802 return make_ui('db', clear_session=False) | |
803 | |
804 @classmethod | |
805 def inject_ui(cls, repo, extras={}): | |
806 from rhodecode.lib.vcs.backends.hg import MercurialRepository | |
807 from rhodecode.lib.vcs.backends.git import GitRepository | |
808 required = (MercurialRepository, GitRepository) | |
809 if not isinstance(repo, required): | |
810 raise Exception('repo must be instance of %s' % required) | |
811 | |
812 # inject ui extra param to log this action via push logger | |
813 for k, v in extras.items(): | |
814 repo._repo.ui.setconfig('rhodecode_extras', k, v) | |
815 | |
816 @classmethod | |
817 def is_valid(cls, repo_name): | |
818 """ | |
819 returns True if given repo name is a valid filesystem repository | |
820 | |
821 :param cls: | |
822 :param repo_name: | |
823 """ | |
824 from rhodecode.lib.utils import is_valid_repo | |
825 | |
826 return is_valid_repo(repo_name, cls.base_path()) | |
827 | |
828 def get_api_data(self): | |
829 """ | |
830 Common function for generating repo api data | |
831 | |
832 """ | |
833 repo = self | |
834 data = dict( | |
835 repo_id=repo.repo_id, | |
836 repo_name=repo.repo_name, | |
837 repo_type=repo.repo_type, | |
838 clone_uri=repo.clone_uri, | |
839 private=repo.private, | |
840 created_on=repo.created_on, | |
841 description=repo.description, | |
842 landing_rev=repo.landing_rev, | |
843 owner=repo.user.username, | |
844 fork_of=repo.fork.repo_name if repo.fork else None | |
845 ) | |
846 | |
847 return data | |
848 | |
849 @classmethod | |
850 def lock(cls, repo, user_id): | |
851 repo.locked = [user_id, time.time()] | |
852 Session().add(repo) | |
853 Session().commit() | |
854 | |
855 @classmethod | |
856 def unlock(cls, repo): | |
857 repo.locked = None | |
858 Session().add(repo) | |
859 Session().commit() | |
860 | |
861 @property | |
862 def last_db_change(self): | |
863 return self.updated_on | |
864 | |
865 #========================================================================== | |
866 # SCM PROPERTIES | |
867 #========================================================================== | |
868 | |
869 def get_changeset(self, rev=None): | |
870 return get_changeset_safe(self.scm_instance, rev) | |
871 | |
872 def get_landing_changeset(self): | |
873 """ | |
874 Returns landing changeset, or if that doesn't exist returns the tip | |
875 """ | |
876 cs = self.get_changeset(self.landing_rev) or self.get_changeset() | |
877 return cs | |
878 | |
879 def update_last_change(self, last_change=None): | |
880 if last_change is None: | |
881 last_change = datetime.datetime.now() | |
882 if self.updated_on is None or self.updated_on != last_change: | |
883 log.debug('updated repo %s with new date %s' % (self, last_change)) | |
884 self.updated_on = last_change | |
885 Session().add(self) | |
886 Session().commit() | |
887 | |
888 @property | |
889 def tip(self): | |
890 return self.get_changeset('tip') | |
891 | |
892 @property | |
893 def author(self): | |
894 return self.tip.author | |
895 | |
896 @property | |
897 def last_change(self): | |
898 return self.scm_instance.last_change | |
899 | |
900 def get_comments(self, revisions=None): | |
901 """ | |
902 Returns comments for this repository grouped by revisions | |
903 | |
904 :param revisions: filter query by revisions only | |
905 """ | |
906 cmts = ChangesetComment.query()\ | |
907 .filter(ChangesetComment.repo == self) | |
908 if revisions: | |
909 cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) | |
910 grouped = defaultdict(list) | |
911 for cmt in cmts.all(): | |
912 grouped[cmt.revision].append(cmt) | |
913 return grouped | |
914 | |
915 def statuses(self, revisions=None): | |
916 """ | |
917 Returns statuses for this repository | |
918 | |
919 :param revisions: list of revisions to get statuses for | |
920 :type revisions: list | |
921 """ | |
922 | |
923 statuses = ChangesetStatus.query()\ | |
924 .filter(ChangesetStatus.repo == self)\ | |
925 .filter(ChangesetStatus.version == 0) | |
926 if revisions: | |
927 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) | |
928 grouped = {} | |
929 | |
930 #maybe we have open new pullrequest without a status ? | |
931 stat = ChangesetStatus.STATUS_UNDER_REVIEW | |
932 status_lbl = ChangesetStatus.get_status_lbl(stat) | |
933 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): | |
934 for rev in pr.revisions: | |
935 pr_id = pr.pull_request_id | |
936 pr_repo = pr.other_repo.repo_name | |
937 grouped[rev] = [stat, status_lbl, pr_id, pr_repo] | |
938 | |
939 for stat in statuses.all(): | |
940 pr_id = pr_repo = None | |
941 if stat.pull_request: | |
942 pr_id = stat.pull_request.pull_request_id | |
943 pr_repo = stat.pull_request.other_repo.repo_name | |
944 grouped[stat.revision] = [str(stat.status), stat.status_lbl, | |
945 pr_id, pr_repo] | |
946 return grouped | |
947 | |
948 #========================================================================== | |
949 # SCM CACHE INSTANCE | |
950 #========================================================================== | |
951 | |
952 @property | |
953 def invalidate(self): | |
954 return CacheInvalidation.invalidate(self.repo_name) | |
955 | |
956 def set_invalidate(self): | |
957 """ | |
958 set a cache for invalidation for this instance | |
959 """ | |
960 CacheInvalidation.set_invalidate(repo_name=self.repo_name) | |
961 | |
962 @LazyProperty | |
963 def scm_instance(self): | |
964 import rhodecode | |
965 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) | |
966 if full_cache: | |
967 return self.scm_instance_cached() | |
968 return self.__get_instance() | |
969 | |
970 def scm_instance_cached(self, cache_map=None): | |
971 @cache_region('long_term') | |
972 def _c(repo_name): | |
973 return self.__get_instance() | |
974 rn = self.repo_name | |
975 log.debug('Getting cached instance of repo') | |
976 | |
977 if cache_map: | |
978 # get using prefilled cache_map | |
979 invalidate_repo = cache_map[self.repo_name] | |
980 if invalidate_repo: | |
981 invalidate_repo = (None if invalidate_repo.cache_active | |
982 else invalidate_repo) | |
983 else: | |
984 # get from invalidate | |
985 invalidate_repo = self.invalidate | |
986 | |
987 if invalidate_repo is not None: | |
988 region_invalidate(_c, None, rn) | |
989 # update our cache | |
990 CacheInvalidation.set_valid(invalidate_repo.cache_key) | |
991 return _c(rn) | |
992 | |
993 def __get_instance(self): | |
994 repo_full_path = self.repo_full_path | |
995 try: | |
996 alias = get_scm(repo_full_path)[0] | |
997 log.debug('Creating instance of %s repository' % alias) | |
998 backend = get_backend(alias) | |
999 except VCSError: | |
1000 log.error(traceback.format_exc()) | |
1001 log.error('Perhaps this repository is in db and not in ' | |
1002 'filesystem run rescan repositories with ' | |
1003 '"destroy old data " option from admin panel') | |
1004 return | |
1005 | |
1006 if alias == 'hg': | |
1007 | |
1008 repo = backend(safe_str(repo_full_path), create=False, | |
1009 baseui=self._ui) | |
1010 # skip hidden web repository | |
1011 if repo._get_hidden(): | |
1012 return | |
1013 else: | |
1014 repo = backend(repo_full_path, create=False) | |
1015 | |
1016 return repo | |
1017 | |
1018 | |
1019 class RepoGroup(Base, BaseModel): | |
1020 __tablename__ = 'groups' | |
1021 __table_args__ = ( | |
1022 UniqueConstraint('group_name', 'group_parent_id'), | |
1023 CheckConstraint('group_id != group_parent_id'), | |
1024 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1025 'mysql_charset': 'utf8'}, | |
1026 ) | |
1027 __mapper_args__ = {'order_by': 'group_name'} | |
1028 | |
1029 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1030 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |
1031 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) | |
1032 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1033 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |
1034 | |
1035 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') | |
1036 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all') | |
1037 | |
1038 parent_group = relationship('RepoGroup', remote_side=group_id) | |
1039 | |
1040 def __init__(self, group_name='', parent_group=None): | |
1041 self.group_name = group_name | |
1042 self.parent_group = parent_group | |
1043 | |
1044 def __unicode__(self): | |
1045 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id, | |
1046 self.group_name) | |
1047 | |
1048 @classmethod | |
1049 def groups_choices(cls, check_perms=False): | |
1050 from webhelpers.html import literal as _literal | |
1051 from rhodecode.model.scm import ScmModel | |
1052 groups = cls.query().all() | |
1053 if check_perms: | |
1054 #filter group user have access to, it's done | |
1055 #magically inside ScmModel based on current user | |
1056 groups = ScmModel().get_repos_groups(groups) | |
1057 repo_groups = [('', '')] | |
1058 sep = ' » ' | |
1059 _name = lambda k: _literal(sep.join(k)) | |
1060 | |
1061 repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) | |
1062 for x in groups]) | |
1063 | |
1064 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) | |
1065 return repo_groups | |
1066 | |
1067 @classmethod | |
1068 def url_sep(cls): | |
1069 return URL_SEP | |
1070 | |
1071 @classmethod | |
1072 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): | |
1073 if case_insensitive: | |
1074 gr = cls.query()\ | |
1075 .filter(cls.group_name.ilike(group_name)) | |
1076 else: | |
1077 gr = cls.query()\ | |
1078 .filter(cls.group_name == group_name) | |
1079 if cache: | |
1080 gr = gr.options(FromCache( | |
1081 "sql_cache_short", | |
1082 "get_group_%s" % _hash_key(group_name) | |
1083 ) | |
1084 ) | |
1085 return gr.scalar() | |
1086 | |
1087 @property | |
1088 def parents(self): | |
1089 parents_recursion_limit = 5 | |
1090 groups = [] | |
1091 if self.parent_group is None: | |
1092 return groups | |
1093 cur_gr = self.parent_group | |
1094 groups.insert(0, cur_gr) | |
1095 cnt = 0 | |
1096 while 1: | |
1097 cnt += 1 | |
1098 gr = getattr(cur_gr, 'parent_group', None) | |
1099 cur_gr = cur_gr.parent_group | |
1100 if gr is None: | |
1101 break | |
1102 if cnt == parents_recursion_limit: | |
1103 # this will prevent accidental infinit loops | |
1104 log.error('group nested more than %s' % | |
1105 parents_recursion_limit) | |
1106 break | |
1107 | |
1108 groups.insert(0, gr) | |
1109 return groups | |
1110 | |
1111 @property | |
1112 def children(self): | |
1113 return RepoGroup.query().filter(RepoGroup.parent_group == self) | |
1114 | |
1115 @property | |
1116 def name(self): | |
1117 return self.group_name.split(RepoGroup.url_sep())[-1] | |
1118 | |
1119 @property | |
1120 def full_path(self): | |
1121 return self.group_name | |
1122 | |
1123 @property | |
1124 def full_path_splitted(self): | |
1125 return self.group_name.split(RepoGroup.url_sep()) | |
1126 | |
1127 @property | |
1128 def repositories(self): | |
1129 return Repository.query()\ | |
1130 .filter(Repository.group == self)\ | |
1131 .order_by(Repository.repo_name) | |
1132 | |
1133 @property | |
1134 def repositories_recursive_count(self): | |
1135 cnt = self.repositories.count() | |
1136 | |
1137 def children_count(group): | |
1138 cnt = 0 | |
1139 for child in group.children: | |
1140 cnt += child.repositories.count() | |
1141 cnt += children_count(child) | |
1142 return cnt | |
1143 | |
1144 return cnt + children_count(self) | |
1145 | |
1146 def recursive_groups_and_repos(self): | |
1147 """ | |
1148 Recursive return all groups, with repositories in those groups | |
1149 """ | |
1150 all_ = [] | |
1151 | |
1152 def _get_members(root_gr): | |
1153 for r in root_gr.repositories: | |
1154 all_.append(r) | |
1155 childs = root_gr.children.all() | |
1156 if childs: | |
1157 for gr in childs: | |
1158 all_.append(gr) | |
1159 _get_members(gr) | |
1160 | |
1161 _get_members(self) | |
1162 return [self] + all_ | |
1163 | |
1164 def get_new_name(self, group_name): | |
1165 """ | |
1166 returns new full group name based on parent and new name | |
1167 | |
1168 :param group_name: | |
1169 """ | |
1170 path_prefix = (self.parent_group.full_path_splitted if | |
1171 self.parent_group else []) | |
1172 return RepoGroup.url_sep().join(path_prefix + [group_name]) | |
1173 | |
1174 | |
1175 class Permission(Base, BaseModel): | |
1176 __tablename__ = 'permissions' | |
1177 __table_args__ = ( | |
1178 Index('p_perm_name_idx', 'permission_name'), | |
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1180 'mysql_charset': 'utf8'}, | |
1181 ) | |
1182 PERMS = [ | |
1183 ('repository.none', _('Repository no access')), | |
1184 ('repository.read', _('Repository read access')), | |
1185 ('repository.write', _('Repository write access')), | |
1186 ('repository.admin', _('Repository admin access')), | |
1187 | |
1188 ('group.none', _('Repositories Group no access')), | |
1189 ('group.read', _('Repositories Group read access')), | |
1190 ('group.write', _('Repositories Group write access')), | |
1191 ('group.admin', _('Repositories Group admin access')), | |
1192 | |
1193 ('hg.admin', _('RhodeCode Administrator')), | |
1194 ('hg.create.none', _('Repository creation disabled')), | |
1195 ('hg.create.repository', _('Repository creation enabled')), | |
1196 ('hg.fork.none', _('Repository forking disabled')), | |
1197 ('hg.fork.repository', _('Repository forking enabled')), | |
1198 ('hg.register.none', _('Register disabled')), | |
1199 ('hg.register.manual_activate', _('Register new user with RhodeCode ' | |
1200 'with manual activation')), | |
1201 | |
1202 ('hg.register.auto_activate', _('Register new user with RhodeCode ' | |
1203 'with auto activation')), | |
1204 ] | |
1205 | |
1206 # defines which permissions are more important higher the more important | |
1207 PERM_WEIGHTS = { | |
1208 'repository.none': 0, | |
1209 'repository.read': 1, | |
1210 'repository.write': 3, | |
1211 'repository.admin': 4, | |
1212 | |
1213 'group.none': 0, | |
1214 'group.read': 1, | |
1215 'group.write': 3, | |
1216 'group.admin': 4, | |
1217 | |
1218 'hg.fork.none': 0, | |
1219 'hg.fork.repository': 1, | |
1220 'hg.create.none': 0, | |
1221 'hg.create.repository':1 | |
1222 } | |
1223 | |
1224 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1225 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1226 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1227 | |
1228 def __unicode__(self): | |
1229 return u"<%s('%s:%s')>" % ( | |
1230 self.__class__.__name__, self.permission_id, self.permission_name | |
1231 ) | |
1232 | |
1233 @classmethod | |
1234 def get_by_key(cls, key): | |
1235 return cls.query().filter(cls.permission_name == key).scalar() | |
1236 | |
1237 @classmethod | |
1238 def get_default_perms(cls, default_user_id): | |
1239 q = Session().query(UserRepoToPerm, Repository, cls)\ | |
1240 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ | |
1241 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ | |
1242 .filter(UserRepoToPerm.user_id == default_user_id) | |
1243 | |
1244 return q.all() | |
1245 | |
1246 @classmethod | |
1247 def get_default_group_perms(cls, default_user_id): | |
1248 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ | |
1249 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ | |
1250 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ | |
1251 .filter(UserRepoGroupToPerm.user_id == default_user_id) | |
1252 | |
1253 return q.all() | |
1254 | |
1255 | |
1256 class UserRepoToPerm(Base, BaseModel): | |
1257 __tablename__ = 'repo_to_perm' | |
1258 __table_args__ = ( | |
1259 UniqueConstraint('user_id', 'repository_id', 'permission_id'), | |
1260 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1261 'mysql_charset': 'utf8'} | |
1262 ) | |
1263 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1265 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1266 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |
1267 | |
1268 user = relationship('User') | |
1269 repository = relationship('Repository') | |
1270 permission = relationship('Permission') | |
1271 | |
1272 @classmethod | |
1273 def create(cls, user, repository, permission): | |
1274 n = cls() | |
1275 n.user = user | |
1276 n.repository = repository | |
1277 n.permission = permission | |
1278 Session().add(n) | |
1279 return n | |
1280 | |
1281 def __unicode__(self): | |
1282 return u'<user:%s => %s >' % (self.user, self.repository) | |
1283 | |
1284 | |
1285 class UserToPerm(Base, BaseModel): | |
1286 __tablename__ = 'user_to_perm' | |
1287 __table_args__ = ( | |
1288 UniqueConstraint('user_id', 'permission_id'), | |
1289 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1290 'mysql_charset': 'utf8'} | |
1291 ) | |
1292 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1293 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1294 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1295 | |
1296 user = relationship('User') | |
1297 permission = relationship('Permission', lazy='joined') | |
1298 | |
1299 | |
1300 class UsersGroupRepoToPerm(Base, BaseModel): | |
1301 __tablename__ = 'users_group_repo_to_perm' | |
1302 __table_args__ = ( | |
1303 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), | |
1304 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1305 'mysql_charset': 'utf8'} | |
1306 ) | |
1307 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1308 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1309 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1310 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |
1311 | |
1312 users_group = relationship('UsersGroup') | |
1313 permission = relationship('Permission') | |
1314 repository = relationship('Repository') | |
1315 | |
1316 @classmethod | |
1317 def create(cls, users_group, repository, permission): | |
1318 n = cls() | |
1319 n.users_group = users_group | |
1320 n.repository = repository | |
1321 n.permission = permission | |
1322 Session().add(n) | |
1323 return n | |
1324 | |
1325 def __unicode__(self): | |
1326 return u'<userGroup:%s => %s >' % (self.users_group, self.repository) | |
1327 | |
1328 | |
1329 class UsersGroupToPerm(Base, BaseModel): | |
1330 __tablename__ = 'users_group_to_perm' | |
1331 __table_args__ = ( | |
1332 UniqueConstraint('users_group_id', 'permission_id',), | |
1333 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1334 'mysql_charset': 'utf8'} | |
1335 ) | |
1336 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1337 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1338 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1339 | |
1340 users_group = relationship('UsersGroup') | |
1341 permission = relationship('Permission') | |
1342 | |
1343 | |
1344 class UserRepoGroupToPerm(Base, BaseModel): | |
1345 __tablename__ = 'user_repo_group_to_perm' | |
1346 __table_args__ = ( | |
1347 UniqueConstraint('user_id', 'group_id', 'permission_id'), | |
1348 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1349 'mysql_charset': 'utf8'} | |
1350 ) | |
1351 | |
1352 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1353 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1354 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) | |
1355 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1356 | |
1357 user = relationship('User') | |
1358 group = relationship('RepoGroup') | |
1359 permission = relationship('Permission') | |
1360 | |
1361 | |
1362 class UsersGroupRepoGroupToPerm(Base, BaseModel): | |
1363 __tablename__ = 'users_group_repo_group_to_perm' | |
1364 __table_args__ = ( | |
1365 UniqueConstraint('users_group_id', 'group_id'), | |
1366 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1367 'mysql_charset': 'utf8'} | |
1368 ) | |
1369 | |
1370 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) | |
1371 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1372 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) | |
1373 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1374 | |
1375 users_group = relationship('UsersGroup') | |
1376 permission = relationship('Permission') | |
1377 group = relationship('RepoGroup') | |
1378 | |
1379 | |
1380 class Statistics(Base, BaseModel): | |
1381 __tablename__ = 'statistics' | |
1382 __table_args__ = ( | |
1383 UniqueConstraint('repository_id'), | |
1384 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1385 'mysql_charset': 'utf8'} | |
1386 ) | |
1387 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1388 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) | |
1389 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) | |
1390 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data | |
1391 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data | |
1392 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data | |
1393 | |
1394 repository = relationship('Repository', single_parent=True) | |
1395 | |
1396 | |
1397 class UserFollowing(Base, BaseModel): | |
1398 __tablename__ = 'user_followings' | |
1399 __table_args__ = ( | |
1400 UniqueConstraint('user_id', 'follows_repository_id'), | |
1401 UniqueConstraint('user_id', 'follows_user_id'), | |
1402 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1403 'mysql_charset': 'utf8'} | |
1404 ) | |
1405 | |
1406 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1407 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1408 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) | |
1409 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |
1410 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
1411 | |
1412 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') | |
1413 | |
1414 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') | |
1415 follows_repository = relationship('Repository', order_by='Repository.repo_name') | |
1416 | |
1417 @classmethod | |
1418 def get_repo_followers(cls, repo_id): | |
1419 return cls.query().filter(cls.follows_repo_id == repo_id) | |
1420 | |
1421 | |
1422 class CacheInvalidation(Base, BaseModel): | |
1423 __tablename__ = 'cache_invalidation' | |
1424 __table_args__ = ( | |
1425 UniqueConstraint('cache_key'), | |
1426 Index('key_idx', 'cache_key'), | |
1427 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1428 'mysql_charset': 'utf8'}, | |
1429 ) | |
1430 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1431 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1432 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1433 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) | |
1434 | |
1435 def __init__(self, cache_key, cache_args=''): | |
1436 self.cache_key = cache_key | |
1437 self.cache_args = cache_args | |
1438 self.cache_active = False | |
1439 | |
1440 def __unicode__(self): | |
1441 return u"<%s('%s:%s')>" % (self.__class__.__name__, | |
1442 self.cache_id, self.cache_key) | |
1443 | |
1444 @property | |
1445 def prefix(self): | |
1446 _split = self.cache_key.split(self.cache_args, 1) | |
1447 if _split and len(_split) == 2: | |
1448 return _split[0] | |
1449 return '' | |
1450 | |
1451 @classmethod | |
1452 def clear_cache(cls): | |
1453 cls.query().delete() | |
1454 | |
1455 @classmethod | |
1456 def _get_key(cls, key): | |
1457 """ | |
1458 Wrapper for generating a key, together with a prefix | |
1459 | |
1460 :param key: | |
1461 """ | |
1462 import rhodecode | |
1463 prefix = '' | |
1464 org_key = key | |
1465 iid = rhodecode.CONFIG.get('instance_id') | |
1466 if iid: | |
1467 prefix = iid | |
1468 | |
1469 return "%s%s" % (prefix, key), prefix, org_key | |
1470 | |
1471 @classmethod | |
1472 def get_by_key(cls, key): | |
1473 return cls.query().filter(cls.cache_key == key).scalar() | |
1474 | |
1475 @classmethod | |
1476 def get_by_repo_name(cls, repo_name): | |
1477 return cls.query().filter(cls.cache_args == repo_name).all() | |
1478 | |
1479 @classmethod | |
1480 def _get_or_create_key(cls, key, repo_name, commit=True): | |
1481 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() | |
1482 if not inv_obj: | |
1483 try: | |
1484 inv_obj = CacheInvalidation(key, repo_name) | |
1485 Session().add(inv_obj) | |
1486 if commit: | |
1487 Session().commit() | |
1488 except Exception: | |
1489 log.error(traceback.format_exc()) | |
1490 Session().rollback() | |
1491 return inv_obj | |
1492 | |
1493 @classmethod | |
1494 def invalidate(cls, key): | |
1495 """ | |
1496 Returns Invalidation object if this given key should be invalidated | |
1497 None otherwise. `cache_active = False` means that this cache | |
1498 state is not valid and needs to be invalidated | |
1499 | |
1500 :param key: | |
1501 """ | |
1502 repo_name = key | |
1503 repo_name = remove_suffix(repo_name, '_README') | |
1504 repo_name = remove_suffix(repo_name, '_RSS') | |
1505 repo_name = remove_suffix(repo_name, '_ATOM') | |
1506 | |
1507 # adds instance prefix | |
1508 key, _prefix, _org_key = cls._get_key(key) | |
1509 inv = cls._get_or_create_key(key, repo_name) | |
1510 | |
1511 if inv and inv.cache_active is False: | |
1512 return inv | |
1513 | |
1514 @classmethod | |
1515 def set_invalidate(cls, key=None, repo_name=None): | |
1516 """ | |
1517 Mark this Cache key for invalidation, either by key or whole | |
1518 cache sets based on repo_name | |
1519 | |
1520 :param key: | |
1521 """ | |
1522 if key: | |
1523 key, _prefix, _org_key = cls._get_key(key) | |
1524 inv_objs = Session().query(cls).filter(cls.cache_key == key).all() | |
1525 elif repo_name: | |
1526 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() | |
1527 | |
1528 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s' | |
1529 % (len(inv_objs), key, repo_name)) | |
1530 try: | |
1531 for inv_obj in inv_objs: | |
1532 inv_obj.cache_active = False | |
1533 Session().add(inv_obj) | |
1534 Session().commit() | |
1535 except Exception: | |
1536 log.error(traceback.format_exc()) | |
1537 Session().rollback() | |
1538 | |
1539 @classmethod | |
1540 def set_valid(cls, key): | |
1541 """ | |
1542 Mark this cache key as active and currently cached | |
1543 | |
1544 :param key: | |
1545 """ | |
1546 inv_obj = cls.get_by_key(key) | |
1547 inv_obj.cache_active = True | |
1548 Session().add(inv_obj) | |
1549 Session().commit() | |
1550 | |
1551 @classmethod | |
1552 def get_cache_map(cls): | |
1553 | |
1554 class cachemapdict(dict): | |
1555 | |
1556 def __init__(self, *args, **kwargs): | |
1557 fixkey = kwargs.get('fixkey') | |
1558 if fixkey: | |
1559 del kwargs['fixkey'] | |
1560 self.fixkey = fixkey | |
1561 super(cachemapdict, self).__init__(*args, **kwargs) | |
1562 | |
1563 def __getattr__(self, name): | |
1564 key = name | |
1565 if self.fixkey: | |
1566 key, _prefix, _org_key = cls._get_key(key) | |
1567 if key in self.__dict__: | |
1568 return self.__dict__[key] | |
1569 else: | |
1570 return self[key] | |
1571 | |
1572 def __getitem__(self, key): | |
1573 if self.fixkey: | |
1574 key, _prefix, _org_key = cls._get_key(key) | |
1575 try: | |
1576 return super(cachemapdict, self).__getitem__(key) | |
1577 except KeyError: | |
1578 return | |
1579 | |
1580 cache_map = cachemapdict(fixkey=True) | |
1581 for obj in cls.query().all(): | |
1582 cache_map[obj.cache_key] = cachemapdict(obj.get_dict()) | |
1583 return cache_map | |
1584 | |
1585 | |
1586 class ChangesetComment(Base, BaseModel): | |
1587 __tablename__ = 'changeset_comments' | |
1588 __table_args__ = ( | |
1589 Index('cc_revision_idx', 'revision'), | |
1590 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1591 'mysql_charset': 'utf8'}, | |
1592 ) | |
1593 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) | |
1594 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
1595 revision = Column('revision', String(40), nullable=True) | |
1596 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) | |
1597 line_no = Column('line_no', Unicode(10), nullable=True) | |
1598 hl_lines = Column('hl_lines', Unicode(512), nullable=True) | |
1599 f_path = Column('f_path', Unicode(1000), nullable=True) | |
1600 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) | |
1601 text = Column('text', UnicodeText(25000), nullable=False) | |
1602 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
1603 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
1604 | |
1605 author = relationship('User', lazy='joined') | |
1606 repo = relationship('Repository') | |
1607 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") | |
1608 pull_request = relationship('PullRequest', lazy='joined') | |
1609 | |
1610 @classmethod | |
1611 def get_users(cls, revision=None, pull_request_id=None): | |
1612 """ | |
1613 Returns user associated with this ChangesetComment. ie those | |
1614 who actually commented | |
1615 | |
1616 :param cls: | |
1617 :param revision: | |
1618 """ | |
1619 q = Session().query(User)\ | |
1620 .join(ChangesetComment.author) | |
1621 if revision: | |
1622 q = q.filter(cls.revision == revision) | |
1623 elif pull_request_id: | |
1624 q = q.filter(cls.pull_request_id == pull_request_id) | |
1625 return q.all() | |
1626 | |
1627 | |
1628 class ChangesetStatus(Base, BaseModel): | |
1629 __tablename__ = 'changeset_statuses' | |
1630 __table_args__ = ( | |
1631 Index('cs_revision_idx', 'revision'), | |
1632 Index('cs_version_idx', 'version'), | |
1633 UniqueConstraint('repo_id', 'revision', 'version'), | |
1634 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1635 'mysql_charset': 'utf8'} | |
1636 ) | |
1637 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' | |
1638 STATUS_APPROVED = 'approved' | |
1639 STATUS_REJECTED = 'rejected' | |
1640 STATUS_UNDER_REVIEW = 'under_review' | |
1641 | |
1642 STATUSES = [ | |
1643 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default | |
1644 (STATUS_APPROVED, _("Approved")), | |
1645 (STATUS_REJECTED, _("Rejected")), | |
1646 (STATUS_UNDER_REVIEW, _("Under Review")), | |
1647 ] | |
1648 | |
1649 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) | |
1650 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
1651 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) | |
1652 revision = Column('revision', String(40), nullable=False) | |
1653 status = Column('status', String(128), nullable=False, default=DEFAULT) | |
1654 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) | |
1655 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) | |
1656 version = Column('version', Integer(), nullable=False, default=0) | |
1657 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) | |
1658 | |
1659 author = relationship('User', lazy='joined') | |
1660 repo = relationship('Repository') | |
1661 comment = relationship('ChangesetComment', lazy='joined') | |
1662 pull_request = relationship('PullRequest', lazy='joined') | |
1663 | |
1664 def __unicode__(self): | |
1665 return u"<%s('%s:%s')>" % ( | |
1666 self.__class__.__name__, | |
1667 self.status, self.author | |
1668 ) | |
1669 | |
1670 @classmethod | |
1671 def get_status_lbl(cls, value): | |
1672 return dict(cls.STATUSES).get(value) | |
1673 | |
1674 @property | |
1675 def status_lbl(self): | |
1676 return ChangesetStatus.get_status_lbl(self.status) | |
1677 | |
1678 | |
1679 class PullRequest(Base, BaseModel): | |
1680 __tablename__ = 'pull_requests' | |
1681 __table_args__ = ( | |
1682 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1683 'mysql_charset': 'utf8'}, | |
1684 ) | |
1685 | |
1686 STATUS_NEW = u'new' | |
1687 STATUS_OPEN = u'open' | |
1688 STATUS_CLOSED = u'closed' | |
1689 | |
1690 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) | |
1691 title = Column('title', Unicode(256), nullable=True) | |
1692 description = Column('description', UnicodeText(10240), nullable=True) | |
1693 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) | |
1694 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
1695 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
1696 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) | |
1697 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max | |
1698 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
1699 org_ref = Column('org_ref', Unicode(256), nullable=False) | |
1700 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
1701 other_ref = Column('other_ref', Unicode(256), nullable=False) | |
1702 | |
1703 @hybrid_property | |
1704 def revisions(self): | |
1705 return self._revisions.split(':') | |
1706 | |
1707 @revisions.setter | |
1708 def revisions(self, val): | |
1709 self._revisions = ':'.join(val) | |
1710 | |
1711 author = relationship('User', lazy='joined') | |
1712 reviewers = relationship('PullRequestReviewers', | |
1713 cascade="all, delete, delete-orphan") | |
1714 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') | |
1715 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') | |
1716 statuses = relationship('ChangesetStatus') | |
1717 comments = relationship('ChangesetComment', | |
1718 cascade="all, delete, delete-orphan") | |
1719 | |
1720 def is_closed(self): | |
1721 return self.status == self.STATUS_CLOSED | |
1722 | |
1723 def __json__(self): | |
1724 return dict( | |
1725 revisions=self.revisions | |
1726 ) | |
1727 | |
1728 | |
1729 class PullRequestReviewers(Base, BaseModel): | |
1730 __tablename__ = 'pull_request_reviewers' | |
1731 __table_args__ = ( | |
1732 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1733 'mysql_charset': 'utf8'}, | |
1734 ) | |
1735 | |
1736 def __init__(self, user=None, pull_request=None): | |
1737 self.user = user | |
1738 self.pull_request = pull_request | |
1739 | |
1740 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) | |
1741 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) | |
1742 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) | |
1743 | |
1744 user = relationship('User') | |
1745 pull_request = relationship('PullRequest') | |
1746 | |
1747 | |
1748 class Notification(Base, BaseModel): | |
1749 __tablename__ = 'notifications' | |
1750 __table_args__ = ( | |
1751 Index('notification_type_idx', 'type'), | |
1752 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1753 'mysql_charset': 'utf8'}, | |
1754 ) | |
1755 | |
1756 TYPE_CHANGESET_COMMENT = u'cs_comment' | |
1757 TYPE_MESSAGE = u'message' | |
1758 TYPE_MENTION = u'mention' | |
1759 TYPE_REGISTRATION = u'registration' | |
1760 TYPE_PULL_REQUEST = u'pull_request' | |
1761 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' | |
1762 | |
1763 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) | |
1764 subject = Column('subject', Unicode(512), nullable=True) | |
1765 body = Column('body', UnicodeText(50000), nullable=True) | |
1766 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) | |
1767 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
1768 type_ = Column('type', Unicode(256)) | |
1769 | |
1770 created_by_user = relationship('User') | |
1771 notifications_to_users = relationship('UserNotification', lazy='joined', | |
1772 cascade="all, delete, delete-orphan") | |
1773 | |
1774 @property | |
1775 def recipients(self): | |
1776 return [x.user for x in UserNotification.query()\ | |
1777 .filter(UserNotification.notification == self)\ | |
1778 .order_by(UserNotification.user_id.asc()).all()] | |
1779 | |
1780 @classmethod | |
1781 def create(cls, created_by, subject, body, recipients, type_=None): | |
1782 if type_ is None: | |
1783 type_ = Notification.TYPE_MESSAGE | |
1784 | |
1785 notification = cls() | |
1786 notification.created_by_user = created_by | |
1787 notification.subject = subject | |
1788 notification.body = body | |
1789 notification.type_ = type_ | |
1790 notification.created_on = datetime.datetime.now() | |
1791 | |
1792 for u in recipients: | |
1793 assoc = UserNotification() | |
1794 assoc.notification = notification | |
1795 u.notifications.append(assoc) | |
1796 Session().add(notification) | |
1797 return notification | |
1798 | |
1799 @property | |
1800 def description(self): | |
1801 from rhodecode.model.notification import NotificationModel | |
1802 return NotificationModel().make_description(self) | |
1803 | |
1804 | |
1805 class UserNotification(Base, BaseModel): | |
1806 __tablename__ = 'user_to_notification' | |
1807 __table_args__ = ( | |
1808 UniqueConstraint('user_id', 'notification_id'), | |
1809 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1810 'mysql_charset': 'utf8'} | |
1811 ) | |
1812 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) | |
1813 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) | |
1814 read = Column('read', Boolean, default=False) | |
1815 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) | |
1816 | |
1817 user = relationship('User', lazy="joined") | |
1818 notification = relationship('Notification', lazy="joined", | |
1819 order_by=lambda: Notification.created_on.desc(),) | |
1820 | |
1821 def mark_as_read(self): | |
1822 self.read = True | |
1823 Session().add(self) | |
1824 | |
1825 | |
1826 class DbMigrateVersion(Base, BaseModel): | |
1827 __tablename__ = 'db_migrate_version' | |
1828 __table_args__ = ( | |
1829 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1830 'mysql_charset': 'utf8'}, | |
1831 ) | |
1832 repository_id = Column('repository_id', String(250), primary_key=True) | |
1833 repository_path = Column('repository_path', Text) | |
1834 version = Column('version', Integer) |