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