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