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