Mercurial > kallithea
comparison rhodecode/lib/dbmigrate/schema/db_2_0_0.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 return safe_str(self.__unicode__()) | |
151 return '<DB:%s>' % (self.__class__.__name__) | |
152 | |
153 | |
154 class RhodeCodeSetting(Base, BaseModel): | |
155 SETTINGS_TYPES = { | |
156 'str': safe_str, | |
157 'int': safe_int, | |
158 'unicode': safe_unicode, | |
159 'bool': str2bool, | |
160 'list': functools.partial(aslist, sep=',') | |
161 } | |
162 __tablename__ = 'rhodecode_settings' | |
163 __table_args__ = ( | |
164 UniqueConstraint('app_settings_name'), | |
165 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
166 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
167 ) | |
168 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
169 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
170 _app_settings_value = Column("app_settings_value", String(4096, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
171 _app_settings_type = Column("app_settings_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
172 | |
173 def __init__(self, key='', val='', type='unicode'): | |
174 self.app_settings_name = key | |
175 self.app_settings_value = val | |
176 self.app_settings_type = type | |
177 | |
178 @validates('_app_settings_value') | |
179 def validate_settings_value(self, key, val): | |
180 assert type(val) == unicode | |
181 return val | |
182 | |
183 @hybrid_property | |
184 def app_settings_value(self): | |
185 v = self._app_settings_value | |
186 _type = self.app_settings_type | |
187 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode'] | |
188 return converter(v) | |
189 | |
190 @app_settings_value.setter | |
191 def app_settings_value(self, val): | |
192 """ | |
193 Setter that will always make sure we use unicode in app_settings_value | |
194 | |
195 :param val: | |
196 """ | |
197 self._app_settings_value = safe_unicode(val) | |
198 | |
199 @hybrid_property | |
200 def app_settings_type(self): | |
201 return self._app_settings_type | |
202 | |
203 @app_settings_type.setter | |
204 def app_settings_type(self, val): | |
205 if val not in self.SETTINGS_TYPES: | |
206 raise Exception('type must be one of %s got %s' | |
207 % (self.SETTINGS_TYPES.keys(), val)) | |
208 self._app_settings_type = val | |
209 | |
210 def __unicode__(self): | |
211 return u"<%s('%s:%s[%s]')>" % ( | |
212 self.__class__.__name__, | |
213 self.app_settings_name, self.app_settings_value, self.app_settings_type | |
214 ) | |
215 | |
216 @classmethod | |
217 def get_by_name(cls, key): | |
218 return cls.query()\ | |
219 .filter(cls.app_settings_name == key).scalar() | |
220 | |
221 @classmethod | |
222 def get_by_name_or_create(cls, key, val='', type='unicode'): | |
223 res = cls.get_by_name(key) | |
224 if not res: | |
225 res = cls(key, val, type) | |
226 return res | |
227 | |
228 @classmethod | |
229 def create_or_update(cls, key, val=Optional(''), type=Optional('unicode')): | |
230 """ | |
231 Creates or updates RhodeCode setting. If updates is triggered it will only | |
232 update parameters that are explicityl set Optional instance will be skipped | |
233 | |
234 :param key: | |
235 :param val: | |
236 :param type: | |
237 :return: | |
238 """ | |
239 res = cls.get_by_name(key) | |
240 if not res: | |
241 val = Optional.extract(val) | |
242 type = Optional.extract(type) | |
243 res = cls(key, val, type) | |
244 else: | |
245 res.app_settings_name = key | |
246 if not isinstance(val, Optional): | |
247 # update if set | |
248 res.app_settings_value = val | |
249 if not isinstance(type, Optional): | |
250 # update if set | |
251 res.app_settings_type = type | |
252 return res | |
253 | |
254 @classmethod | |
255 def get_app_settings(cls, cache=False): | |
256 | |
257 ret = cls.query() | |
258 | |
259 if cache: | |
260 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings")) | |
261 | |
262 if not ret: | |
263 raise Exception('Could not get application settings !') | |
264 settings = {} | |
265 for each in ret: | |
266 settings['rhodecode_' + each.app_settings_name] = \ | |
267 each.app_settings_value | |
268 | |
269 return settings | |
270 | |
271 @classmethod | |
272 def get_auth_plugins(cls, cache=False): | |
273 auth_plugins = cls.get_by_name("auth_plugins").app_settings_value | |
274 return auth_plugins | |
275 | |
276 @classmethod | |
277 def get_auth_settings(cls, cache=False): | |
278 ret = cls.query()\ | |
279 .filter(cls.app_settings_name.startswith('auth_')).all() | |
280 fd = {} | |
281 for row in ret: | |
282 fd.update({row.app_settings_name: row.app_settings_value}) | |
283 | |
284 return fd | |
285 | |
286 @classmethod | |
287 def get_default_repo_settings(cls, cache=False, strip_prefix=False): | |
288 ret = cls.query()\ | |
289 .filter(cls.app_settings_name.startswith('default_')).all() | |
290 fd = {} | |
291 for row in ret: | |
292 key = row.app_settings_name | |
293 if strip_prefix: | |
294 key = remove_prefix(key, prefix='default_') | |
295 fd.update({key: row.app_settings_value}) | |
296 | |
297 return fd | |
298 | |
299 @classmethod | |
300 def get_server_info(cls): | |
301 import pkg_resources | |
302 import platform | |
303 import rhodecode | |
304 from rhodecode.lib.utils import check_git_version | |
305 mods = [(p.project_name, p.version) for p in pkg_resources.working_set] | |
306 info = { | |
307 'modules': sorted(mods, key=lambda k: k[0].lower()), | |
308 'py_version': platform.python_version(), | |
309 'platform': platform.platform(), | |
310 'rhodecode_version': rhodecode.__version__, | |
311 'git_version': str(check_git_version()), | |
312 'git_path': rhodecode.CONFIG.get('git_path') | |
313 } | |
314 return info | |
315 | |
316 | |
317 class RhodeCodeUi(Base, BaseModel): | |
318 __tablename__ = 'rhodecode_ui' | |
319 __table_args__ = ( | |
320 UniqueConstraint('ui_key'), | |
321 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
322 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
323 ) | |
324 | |
325 HOOK_UPDATE = 'changegroup.update' | |
326 HOOK_REPO_SIZE = 'changegroup.repo_size' | |
327 HOOK_PUSH = 'changegroup.push_logger' | |
328 HOOK_PRE_PUSH = 'prechangegroup.pre_push' | |
329 HOOK_PULL = 'outgoing.pull_logger' | |
330 HOOK_PRE_PULL = 'preoutgoing.pre_pull' | |
331 | |
332 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
333 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
334 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
335 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
336 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True) | |
337 | |
338 # def __init__(self, section='', key='', value=''): | |
339 # self.ui_section = section | |
340 # self.ui_key = key | |
341 # self.ui_value = value | |
342 | |
343 @classmethod | |
344 def get_by_key(cls, key): | |
345 return cls.query().filter(cls.ui_key == key).scalar() | |
346 | |
347 @classmethod | |
348 def get_builtin_hooks(cls): | |
349 q = cls.query() | |
350 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, | |
351 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, | |
352 cls.HOOK_PULL, cls.HOOK_PRE_PULL])) | |
353 return q.all() | |
354 | |
355 @classmethod | |
356 def get_custom_hooks(cls): | |
357 q = cls.query() | |
358 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE, | |
359 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH, | |
360 cls.HOOK_PULL, cls.HOOK_PRE_PULL])) | |
361 q = q.filter(cls.ui_section == 'hooks') | |
362 return q.all() | |
363 | |
364 @classmethod | |
365 def get_repos_location(cls): | |
366 return cls.get_by_key('/').ui_value | |
367 | |
368 @classmethod | |
369 def create_or_update_hook(cls, key, val): | |
370 new_ui = cls.get_by_key(key) or cls() | |
371 new_ui.ui_section = 'hooks' | |
372 new_ui.ui_active = True | |
373 new_ui.ui_key = key | |
374 new_ui.ui_value = val | |
375 | |
376 Session().add(new_ui) | |
377 | |
378 def __repr__(self): | |
379 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key, | |
380 self.ui_value) | |
381 | |
382 | |
383 class User(Base, BaseModel): | |
384 __tablename__ = 'users' | |
385 __table_args__ = ( | |
386 UniqueConstraint('username'), UniqueConstraint('email'), | |
387 Index('u_username_idx', 'username'), | |
388 Index('u_email_idx', 'email'), | |
389 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
390 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
391 ) | |
392 DEFAULT_USER = 'default' | |
393 | |
394 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
395 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
396 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
397 active = Column("active", Boolean(), nullable=True, unique=None, default=True) | |
398 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False) | |
399 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
400 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
401 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
402 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None) | |
403 extern_type = Column("extern_type", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
404 extern_name = Column("extern_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
405 #for migration reasons, this is going to be later deleted | |
406 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
407 | |
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 # don't trigger lazy load for migrations | |
721 #members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined") | |
722 users_group_to_perm = relationship('UserGroupToPerm', cascade='all') | |
723 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all') | |
724 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') | |
725 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all') | |
726 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all') | |
727 | |
728 user = relationship('User') | |
729 | |
730 def __unicode__(self): | |
731 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, | |
732 self.users_group_id, | |
733 self.users_group_name) | |
734 | |
735 @classmethod | |
736 def get_by_group_name(cls, group_name, cache=False, | |
737 case_insensitive=False): | |
738 if case_insensitive: | |
739 q = cls.query().filter(cls.users_group_name.ilike(group_name)) | |
740 else: | |
741 q = cls.query().filter(cls.users_group_name == group_name) | |
742 if cache: | |
743 q = q.options(FromCache( | |
744 "sql_cache_short", | |
745 "get_user_%s" % _hash_key(group_name) | |
746 ) | |
747 ) | |
748 return q.scalar() | |
749 | |
750 @classmethod | |
751 def get(cls, user_group_id, cache=False): | |
752 user_group = cls.query() | |
753 if cache: | |
754 user_group = user_group.options(FromCache("sql_cache_short", | |
755 "get_users_group_%s" % user_group_id)) | |
756 return user_group.get(user_group_id) | |
757 | |
758 def get_api_data(self, with_members=True): | |
759 user_group = self | |
760 | |
761 data = dict( | |
762 users_group_id=user_group.users_group_id, | |
763 group_name=user_group.users_group_name, | |
764 group_description=user_group.user_group_description, | |
765 active=user_group.users_group_active, | |
766 owner=user_group.user.username, | |
767 ) | |
768 if with_members: | |
769 members = [] | |
770 for user in user_group.members: | |
771 user = user.user | |
772 members.append(user.get_api_data()) | |
773 data['members'] = members | |
774 | |
775 return data | |
776 | |
777 | |
778 class UserGroupMember(Base, BaseModel): | |
779 __tablename__ = 'users_groups_members' | |
780 __table_args__ = ( | |
781 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
782 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
783 ) | |
784 | |
785 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
786 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
787 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
788 | |
789 user = relationship('User', lazy='joined') | |
790 users_group = relationship('UserGroup') | |
791 | |
792 def __init__(self, gr_id='', u_id=''): | |
793 self.users_group_id = gr_id | |
794 self.user_id = u_id | |
795 | |
796 | |
797 class RepositoryField(Base, BaseModel): | |
798 __tablename__ = 'repositories_fields' | |
799 __table_args__ = ( | |
800 UniqueConstraint('repository_id', 'field_key'), # no-multi field | |
801 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
803 ) | |
804 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields | |
805 | |
806 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
807 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |
808 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None)) | |
809 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) | |
810 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False) | |
811 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False) | |
812 field_type = Column("field_type", String(256), nullable=False, unique=None) | |
813 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
814 | |
815 repository = relationship('Repository') | |
816 | |
817 @property | |
818 def field_key_prefixed(self): | |
819 return 'ex_%s' % self.field_key | |
820 | |
821 @classmethod | |
822 def un_prefix_key(cls, key): | |
823 if key.startswith(cls.PREFIX): | |
824 return key[len(cls.PREFIX):] | |
825 return key | |
826 | |
827 @classmethod | |
828 def get_by_key_name(cls, key, repo): | |
829 row = cls.query()\ | |
830 .filter(cls.repository == repo)\ | |
831 .filter(cls.field_key == key).scalar() | |
832 return row | |
833 | |
834 | |
835 class Repository(Base, BaseModel): | |
836 __tablename__ = 'repositories' | |
837 __table_args__ = ( | |
838 UniqueConstraint('repo_name'), | |
839 Index('r_repo_name_idx', 'repo_name'), | |
840 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
841 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
842 ) | |
843 | |
844 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
845 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |
846 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
847 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |
848 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) | |
849 private = Column("private", Boolean(), nullable=True, unique=None, default=None) | |
850 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True) | |
851 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) | |
852 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
853 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
854 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
855 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None) | |
856 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |
857 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) | |
858 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data | |
859 | |
860 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None) | |
861 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None) | |
862 | |
863 user = relationship('User') | |
864 fork = relationship('Repository', remote_side=repo_id) | |
865 group = relationship('RepoGroup') | |
866 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id') | |
867 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all') | |
868 stats = relationship('Statistics', cascade='all', uselist=False) | |
869 | |
870 followers = relationship('UserFollowing', | |
871 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', | |
872 cascade='all') | |
873 extra_fields = relationship('RepositoryField', | |
874 cascade="all, delete, delete-orphan") | |
875 | |
876 logs = relationship('UserLog') | |
877 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan") | |
878 | |
879 pull_requests_org = relationship('PullRequest', | |
880 primaryjoin='PullRequest.org_repo_id==Repository.repo_id', | |
881 cascade="all, delete, delete-orphan") | |
882 | |
883 pull_requests_other = relationship('PullRequest', | |
884 primaryjoin='PullRequest.other_repo_id==Repository.repo_id', | |
885 cascade="all, delete, delete-orphan") | |
886 | |
887 def __unicode__(self): | |
888 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id, | |
889 safe_unicode(self.repo_name)) | |
890 | |
891 @hybrid_property | |
892 def locked(self): | |
893 # always should return [user_id, timelocked] | |
894 if self._locked: | |
895 _lock_info = self._locked.split(':') | |
896 return int(_lock_info[0]), _lock_info[1] | |
897 return [None, None] | |
898 | |
899 @locked.setter | |
900 def locked(self, val): | |
901 if val and isinstance(val, (list, tuple)): | |
902 self._locked = ':'.join(map(str, val)) | |
903 else: | |
904 self._locked = None | |
905 | |
906 @hybrid_property | |
907 def changeset_cache(self): | |
908 from rhodecode.lib.vcs.backends.base import EmptyChangeset | |
909 dummy = EmptyChangeset().__json__() | |
910 if not self._changeset_cache: | |
911 return dummy | |
912 try: | |
913 return json.loads(self._changeset_cache) | |
914 except TypeError: | |
915 return dummy | |
916 | |
917 @changeset_cache.setter | |
918 def changeset_cache(self, val): | |
919 try: | |
920 self._changeset_cache = json.dumps(val) | |
921 except Exception: | |
922 log.error(traceback.format_exc()) | |
923 | |
924 @classmethod | |
925 def url_sep(cls): | |
926 return URL_SEP | |
927 | |
928 @classmethod | |
929 def normalize_repo_name(cls, repo_name): | |
930 """ | |
931 Normalizes os specific repo_name to the format internally stored inside | |
932 dabatabase using URL_SEP | |
933 | |
934 :param cls: | |
935 :param repo_name: | |
936 """ | |
937 return cls.url_sep().join(repo_name.split(os.sep)) | |
938 | |
939 @classmethod | |
940 def get_by_repo_name(cls, repo_name): | |
941 q = Session().query(cls).filter(cls.repo_name == repo_name) | |
942 q = q.options(joinedload(Repository.fork))\ | |
943 .options(joinedload(Repository.user))\ | |
944 .options(joinedload(Repository.group)) | |
945 return q.scalar() | |
946 | |
947 @classmethod | |
948 def get_by_full_path(cls, repo_full_path): | |
949 repo_name = repo_full_path.split(cls.base_path(), 1)[-1] | |
950 repo_name = cls.normalize_repo_name(repo_name) | |
951 return cls.get_by_repo_name(repo_name.strip(URL_SEP)) | |
952 | |
953 @classmethod | |
954 def get_repo_forks(cls, repo_id): | |
955 return cls.query().filter(Repository.fork_id == repo_id) | |
956 | |
957 @classmethod | |
958 def base_path(cls): | |
959 """ | |
960 Returns base path when all repos are stored | |
961 | |
962 :param cls: | |
963 """ | |
964 q = Session().query(RhodeCodeUi)\ | |
965 .filter(RhodeCodeUi.ui_key == cls.url_sep()) | |
966 q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |
967 return q.one().ui_value | |
968 | |
969 @property | |
970 def forks(self): | |
971 """ | |
972 Return forks of this repo | |
973 """ | |
974 return Repository.get_repo_forks(self.repo_id) | |
975 | |
976 @property | |
977 def parent(self): | |
978 """ | |
979 Returns fork parent | |
980 """ | |
981 return self.fork | |
982 | |
983 @property | |
984 def just_name(self): | |
985 return self.repo_name.split(Repository.url_sep())[-1] | |
986 | |
987 @property | |
988 def groups_with_parents(self): | |
989 groups = [] | |
990 if self.group is None: | |
991 return groups | |
992 | |
993 cur_gr = self.group | |
994 groups.insert(0, cur_gr) | |
995 while 1: | |
996 gr = getattr(cur_gr, 'parent_group', None) | |
997 cur_gr = cur_gr.parent_group | |
998 if gr is None: | |
999 break | |
1000 groups.insert(0, gr) | |
1001 | |
1002 return groups | |
1003 | |
1004 @property | |
1005 def groups_and_repo(self): | |
1006 return self.groups_with_parents, self.just_name, self.repo_name | |
1007 | |
1008 @LazyProperty | |
1009 def repo_path(self): | |
1010 """ | |
1011 Returns base full path for that repository means where it actually | |
1012 exists on a filesystem | |
1013 """ | |
1014 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == | |
1015 Repository.url_sep()) | |
1016 q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |
1017 return q.one().ui_value | |
1018 | |
1019 @property | |
1020 def repo_full_path(self): | |
1021 p = [self.repo_path] | |
1022 # we need to split the name by / since this is how we store the | |
1023 # names in the database, but that eventually needs to be converted | |
1024 # into a valid system path | |
1025 p += self.repo_name.split(Repository.url_sep()) | |
1026 return os.path.join(*map(safe_unicode, p)) | |
1027 | |
1028 @property | |
1029 def cache_keys(self): | |
1030 """ | |
1031 Returns associated cache keys for that repo | |
1032 """ | |
1033 return CacheInvalidation.query()\ | |
1034 .filter(CacheInvalidation.cache_args == self.repo_name)\ | |
1035 .order_by(CacheInvalidation.cache_key)\ | |
1036 .all() | |
1037 | |
1038 def get_new_name(self, repo_name): | |
1039 """ | |
1040 returns new full repository name based on assigned group and new new | |
1041 | |
1042 :param group_name: | |
1043 """ | |
1044 path_prefix = self.group.full_path_splitted if self.group else [] | |
1045 return Repository.url_sep().join(path_prefix + [repo_name]) | |
1046 | |
1047 @property | |
1048 def _ui(self): | |
1049 """ | |
1050 Creates an db based ui object for this repository | |
1051 """ | |
1052 from rhodecode.lib.utils import make_ui | |
1053 return make_ui('db', clear_session=False) | |
1054 | |
1055 @classmethod | |
1056 def is_valid(cls, repo_name): | |
1057 """ | |
1058 returns True if given repo name is a valid filesystem repository | |
1059 | |
1060 :param cls: | |
1061 :param repo_name: | |
1062 """ | |
1063 from rhodecode.lib.utils import is_valid_repo | |
1064 | |
1065 return is_valid_repo(repo_name, cls.base_path()) | |
1066 | |
1067 def get_api_data(self): | |
1068 """ | |
1069 Common function for generating repo api data | |
1070 | |
1071 """ | |
1072 repo = self | |
1073 data = dict( | |
1074 repo_id=repo.repo_id, | |
1075 repo_name=repo.repo_name, | |
1076 repo_type=repo.repo_type, | |
1077 clone_uri=repo.clone_uri, | |
1078 private=repo.private, | |
1079 created_on=repo.created_on, | |
1080 description=repo.description, | |
1081 landing_rev=repo.landing_rev, | |
1082 owner=repo.user.username, | |
1083 fork_of=repo.fork.repo_name if repo.fork else None, | |
1084 enable_statistics=repo.enable_statistics, | |
1085 enable_locking=repo.enable_locking, | |
1086 enable_downloads=repo.enable_downloads, | |
1087 last_changeset=repo.changeset_cache, | |
1088 locked_by=User.get(self.locked[0]).get_api_data() \ | |
1089 if self.locked[0] else None, | |
1090 locked_date=time_to_datetime(self.locked[1]) \ | |
1091 if self.locked[1] else None | |
1092 ) | |
1093 rc_config = RhodeCodeSetting.get_app_settings() | |
1094 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields')) | |
1095 if repository_fields: | |
1096 for f in self.extra_fields: | |
1097 data[f.field_key_prefixed] = f.field_value | |
1098 | |
1099 return data | |
1100 | |
1101 @classmethod | |
1102 def lock(cls, repo, user_id, lock_time=None): | |
1103 if not lock_time: | |
1104 lock_time = time.time() | |
1105 repo.locked = [user_id, lock_time] | |
1106 Session().add(repo) | |
1107 Session().commit() | |
1108 | |
1109 @classmethod | |
1110 def unlock(cls, repo): | |
1111 repo.locked = None | |
1112 Session().add(repo) | |
1113 Session().commit() | |
1114 | |
1115 @classmethod | |
1116 def getlock(cls, repo): | |
1117 return repo.locked | |
1118 | |
1119 @property | |
1120 def last_db_change(self): | |
1121 return self.updated_on | |
1122 | |
1123 def clone_url(self, **override): | |
1124 from pylons import url | |
1125 from urlparse import urlparse | |
1126 import urllib | |
1127 parsed_url = urlparse(url('home', qualified=True)) | |
1128 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s' | |
1129 decoded_path = safe_unicode(urllib.unquote(parsed_url.path)) | |
1130 args = { | |
1131 'user': '', | |
1132 'pass': '', | |
1133 'scheme': parsed_url.scheme, | |
1134 'netloc': parsed_url.netloc, | |
1135 'prefix': decoded_path, | |
1136 'path': self.repo_name | |
1137 } | |
1138 | |
1139 args.update(override) | |
1140 return default_clone_uri % args | |
1141 | |
1142 #========================================================================== | |
1143 # SCM PROPERTIES | |
1144 #========================================================================== | |
1145 | |
1146 def get_changeset(self, rev=None): | |
1147 return get_changeset_safe(self.scm_instance, rev) | |
1148 | |
1149 def get_landing_changeset(self): | |
1150 """ | |
1151 Returns landing changeset, or if that doesn't exist returns the tip | |
1152 """ | |
1153 cs = self.get_changeset(self.landing_rev) or self.get_changeset() | |
1154 return cs | |
1155 | |
1156 def update_changeset_cache(self, cs_cache=None): | |
1157 """ | |
1158 Update cache of last changeset for repository, keys should be:: | |
1159 | |
1160 short_id | |
1161 raw_id | |
1162 revision | |
1163 message | |
1164 date | |
1165 author | |
1166 | |
1167 :param cs_cache: | |
1168 """ | |
1169 from rhodecode.lib.vcs.backends.base import BaseChangeset | |
1170 if cs_cache is None: | |
1171 cs_cache = EmptyChangeset() | |
1172 # use no-cache version here | |
1173 scm_repo = self.scm_instance_no_cache() | |
1174 if scm_repo: | |
1175 cs_cache = scm_repo.get_changeset() | |
1176 | |
1177 if isinstance(cs_cache, BaseChangeset): | |
1178 cs_cache = cs_cache.__json__() | |
1179 | |
1180 if (cs_cache != self.changeset_cache or not self.changeset_cache): | |
1181 _default = datetime.datetime.fromtimestamp(0) | |
1182 last_change = cs_cache.get('date') or _default | |
1183 log.debug('updated repo %s with new cs cache %s' | |
1184 % (self.repo_name, cs_cache)) | |
1185 self.updated_on = last_change | |
1186 self.changeset_cache = cs_cache | |
1187 Session().add(self) | |
1188 Session().commit() | |
1189 else: | |
1190 log.debug('Skipping repo:%s already with latest changes' | |
1191 % self.repo_name) | |
1192 | |
1193 @property | |
1194 def tip(self): | |
1195 return self.get_changeset('tip') | |
1196 | |
1197 @property | |
1198 def author(self): | |
1199 return self.tip.author | |
1200 | |
1201 @property | |
1202 def last_change(self): | |
1203 return self.scm_instance.last_change | |
1204 | |
1205 def get_comments(self, revisions=None): | |
1206 """ | |
1207 Returns comments for this repository grouped by revisions | |
1208 | |
1209 :param revisions: filter query by revisions only | |
1210 """ | |
1211 cmts = ChangesetComment.query()\ | |
1212 .filter(ChangesetComment.repo == self) | |
1213 if revisions: | |
1214 cmts = cmts.filter(ChangesetComment.revision.in_(revisions)) | |
1215 grouped = collections.defaultdict(list) | |
1216 for cmt in cmts.all(): | |
1217 grouped[cmt.revision].append(cmt) | |
1218 return grouped | |
1219 | |
1220 def statuses(self, revisions=None): | |
1221 """ | |
1222 Returns statuses for this repository | |
1223 | |
1224 :param revisions: list of revisions to get statuses for | |
1225 """ | |
1226 | |
1227 statuses = ChangesetStatus.query()\ | |
1228 .filter(ChangesetStatus.repo == self)\ | |
1229 .filter(ChangesetStatus.version == 0) | |
1230 if revisions: | |
1231 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions)) | |
1232 grouped = {} | |
1233 | |
1234 #maybe we have open new pullrequest without a status ? | |
1235 stat = ChangesetStatus.STATUS_UNDER_REVIEW | |
1236 status_lbl = ChangesetStatus.get_status_lbl(stat) | |
1237 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all(): | |
1238 for rev in pr.revisions: | |
1239 pr_id = pr.pull_request_id | |
1240 pr_repo = pr.other_repo.repo_name | |
1241 grouped[rev] = [stat, status_lbl, pr_id, pr_repo] | |
1242 | |
1243 for stat in statuses.all(): | |
1244 pr_id = pr_repo = None | |
1245 if stat.pull_request: | |
1246 pr_id = stat.pull_request.pull_request_id | |
1247 pr_repo = stat.pull_request.other_repo.repo_name | |
1248 grouped[stat.revision] = [str(stat.status), stat.status_lbl, | |
1249 pr_id, pr_repo] | |
1250 return grouped | |
1251 | |
1252 def _repo_size(self): | |
1253 from rhodecode.lib import helpers as h | |
1254 log.debug('calculating repository size...') | |
1255 return h.format_byte_size(self.scm_instance.size) | |
1256 | |
1257 #========================================================================== | |
1258 # SCM CACHE INSTANCE | |
1259 #========================================================================== | |
1260 | |
1261 def set_invalidate(self): | |
1262 """ | |
1263 Mark caches of this repo as invalid. | |
1264 """ | |
1265 CacheInvalidation.set_invalidate(self.repo_name) | |
1266 | |
1267 def scm_instance_no_cache(self): | |
1268 return self.__get_instance() | |
1269 | |
1270 @property | |
1271 def scm_instance(self): | |
1272 import rhodecode | |
1273 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache')) | |
1274 if full_cache: | |
1275 return self.scm_instance_cached() | |
1276 return self.__get_instance() | |
1277 | |
1278 def scm_instance_cached(self, valid_cache_keys=None): | |
1279 @cache_region('long_term') | |
1280 def _c(repo_name): | |
1281 return self.__get_instance() | |
1282 rn = self.repo_name | |
1283 | |
1284 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys) | |
1285 if not valid: | |
1286 log.debug('Cache for %s invalidated, getting new object' % (rn)) | |
1287 region_invalidate(_c, None, rn) | |
1288 else: | |
1289 log.debug('Getting obj for %s from cache' % (rn)) | |
1290 return _c(rn) | |
1291 | |
1292 def __get_instance(self): | |
1293 repo_full_path = self.repo_full_path | |
1294 try: | |
1295 alias = get_scm(repo_full_path)[0] | |
1296 log.debug('Creating instance of %s repository from %s' | |
1297 % (alias, repo_full_path)) | |
1298 backend = get_backend(alias) | |
1299 except VCSError: | |
1300 log.error(traceback.format_exc()) | |
1301 log.error('Perhaps this repository is in db and not in ' | |
1302 'filesystem run rescan repositories with ' | |
1303 '"destroy old data " option from admin panel') | |
1304 return | |
1305 | |
1306 if alias == 'hg': | |
1307 | |
1308 repo = backend(safe_str(repo_full_path), create=False, | |
1309 baseui=self._ui) | |
1310 # skip hidden web repository | |
1311 if repo._get_hidden(): | |
1312 return | |
1313 else: | |
1314 repo = backend(repo_full_path, create=False) | |
1315 | |
1316 return repo | |
1317 | |
1318 | |
1319 class RepoGroup(Base, BaseModel): | |
1320 __tablename__ = 'groups' | |
1321 __table_args__ = ( | |
1322 UniqueConstraint('group_name', 'group_parent_id'), | |
1323 CheckConstraint('group_id != group_parent_id'), | |
1324 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1325 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
1326 ) | |
1327 __mapper_args__ = {'order_by': 'group_name'} | |
1328 | |
1329 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1330 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) | |
1331 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None) | |
1332 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1333 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False) | |
1334 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None) | |
1335 | |
1336 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id') | |
1337 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all') | |
1338 parent_group = relationship('RepoGroup', remote_side=group_id) | |
1339 user = relationship('User') | |
1340 | |
1341 def __init__(self, group_name='', parent_group=None): | |
1342 self.group_name = group_name | |
1343 self.parent_group = parent_group | |
1344 | |
1345 def __unicode__(self): | |
1346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id, | |
1347 self.group_name) | |
1348 | |
1349 @classmethod | |
1350 def groups_choices(cls, groups=None, show_empty_group=True): | |
1351 from webhelpers.html import literal as _literal | |
1352 if not groups: | |
1353 groups = cls.query().all() | |
1354 | |
1355 repo_groups = [] | |
1356 if show_empty_group: | |
1357 repo_groups = [('-1', u'-- %s --' % _('top level'))] | |
1358 sep = ' » ' | |
1359 _name = lambda k: _literal(sep.join(k)) | |
1360 | |
1361 repo_groups.extend([(x.group_id, _name(x.full_path_splitted)) | |
1362 for x in groups]) | |
1363 | |
1364 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0]) | |
1365 return repo_groups | |
1366 | |
1367 @classmethod | |
1368 def url_sep(cls): | |
1369 return URL_SEP | |
1370 | |
1371 @classmethod | |
1372 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): | |
1373 if case_insensitive: | |
1374 gr = cls.query()\ | |
1375 .filter(cls.group_name.ilike(group_name)) | |
1376 else: | |
1377 gr = cls.query()\ | |
1378 .filter(cls.group_name == group_name) | |
1379 if cache: | |
1380 gr = gr.options(FromCache( | |
1381 "sql_cache_short", | |
1382 "get_group_%s" % _hash_key(group_name) | |
1383 ) | |
1384 ) | |
1385 return gr.scalar() | |
1386 | |
1387 @property | |
1388 def parents(self): | |
1389 parents_recursion_limit = 5 | |
1390 groups = [] | |
1391 if self.parent_group is None: | |
1392 return groups | |
1393 cur_gr = self.parent_group | |
1394 groups.insert(0, cur_gr) | |
1395 cnt = 0 | |
1396 while 1: | |
1397 cnt += 1 | |
1398 gr = getattr(cur_gr, 'parent_group', None) | |
1399 cur_gr = cur_gr.parent_group | |
1400 if gr is None: | |
1401 break | |
1402 if cnt == parents_recursion_limit: | |
1403 # this will prevent accidental infinit loops | |
1404 log.error('group nested more than %s' % | |
1405 parents_recursion_limit) | |
1406 break | |
1407 | |
1408 groups.insert(0, gr) | |
1409 return groups | |
1410 | |
1411 @property | |
1412 def children(self): | |
1413 return RepoGroup.query().filter(RepoGroup.parent_group == self) | |
1414 | |
1415 @property | |
1416 def name(self): | |
1417 return self.group_name.split(RepoGroup.url_sep())[-1] | |
1418 | |
1419 @property | |
1420 def full_path(self): | |
1421 return self.group_name | |
1422 | |
1423 @property | |
1424 def full_path_splitted(self): | |
1425 return self.group_name.split(RepoGroup.url_sep()) | |
1426 | |
1427 @property | |
1428 def repositories(self): | |
1429 return Repository.query()\ | |
1430 .filter(Repository.group == self)\ | |
1431 .order_by(Repository.repo_name) | |
1432 | |
1433 @property | |
1434 def repositories_recursive_count(self): | |
1435 cnt = self.repositories.count() | |
1436 | |
1437 def children_count(group): | |
1438 cnt = 0 | |
1439 for child in group.children: | |
1440 cnt += child.repositories.count() | |
1441 cnt += children_count(child) | |
1442 return cnt | |
1443 | |
1444 return cnt + children_count(self) | |
1445 | |
1446 def _recursive_objects(self, include_repos=True): | |
1447 all_ = [] | |
1448 | |
1449 def _get_members(root_gr): | |
1450 if include_repos: | |
1451 for r in root_gr.repositories: | |
1452 all_.append(r) | |
1453 childs = root_gr.children.all() | |
1454 if childs: | |
1455 for gr in childs: | |
1456 all_.append(gr) | |
1457 _get_members(gr) | |
1458 | |
1459 _get_members(self) | |
1460 return [self] + all_ | |
1461 | |
1462 def recursive_groups_and_repos(self): | |
1463 """ | |
1464 Recursive return all groups, with repositories in those groups | |
1465 """ | |
1466 return self._recursive_objects() | |
1467 | |
1468 def recursive_groups(self): | |
1469 """ | |
1470 Returns all children groups for this group including children of children | |
1471 """ | |
1472 return self._recursive_objects(include_repos=False) | |
1473 | |
1474 def get_new_name(self, group_name): | |
1475 """ | |
1476 returns new full group name based on parent and new name | |
1477 | |
1478 :param group_name: | |
1479 """ | |
1480 path_prefix = (self.parent_group.full_path_splitted if | |
1481 self.parent_group else []) | |
1482 return RepoGroup.url_sep().join(path_prefix + [group_name]) | |
1483 | |
1484 def get_api_data(self): | |
1485 """ | |
1486 Common function for generating api data | |
1487 | |
1488 """ | |
1489 group = self | |
1490 data = dict( | |
1491 group_id=group.group_id, | |
1492 group_name=group.group_name, | |
1493 group_description=group.group_description, | |
1494 parent_group=group.parent_group.group_name if group.parent_group else None, | |
1495 repositories=[x.repo_name for x in group.repositories], | |
1496 owner=group.user.username | |
1497 ) | |
1498 return data | |
1499 | |
1500 | |
1501 class Permission(Base, BaseModel): | |
1502 __tablename__ = 'permissions' | |
1503 __table_args__ = ( | |
1504 Index('p_perm_name_idx', 'permission_name'), | |
1505 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1506 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
1507 ) | |
1508 PERMS = [ | |
1509 ('hg.admin', _('RhodeCode Administrator')), | |
1510 | |
1511 ('repository.none', _('Repository no access')), | |
1512 ('repository.read', _('Repository read access')), | |
1513 ('repository.write', _('Repository write access')), | |
1514 ('repository.admin', _('Repository admin access')), | |
1515 | |
1516 ('group.none', _('Repository group no access')), | |
1517 ('group.read', _('Repository group read access')), | |
1518 ('group.write', _('Repository group write access')), | |
1519 ('group.admin', _('Repository group admin access')), | |
1520 | |
1521 ('usergroup.none', _('User group no access')), | |
1522 ('usergroup.read', _('User group read access')), | |
1523 ('usergroup.write', _('User group write access')), | |
1524 ('usergroup.admin', _('User group admin access')), | |
1525 | |
1526 ('hg.repogroup.create.false', _('Repository Group creation disabled')), | |
1527 ('hg.repogroup.create.true', _('Repository Group creation enabled')), | |
1528 | |
1529 ('hg.usergroup.create.false', _('User Group creation disabled')), | |
1530 ('hg.usergroup.create.true', _('User Group creation enabled')), | |
1531 | |
1532 ('hg.create.none', _('Repository creation disabled')), | |
1533 ('hg.create.repository', _('Repository creation enabled')), | |
1534 | |
1535 ('hg.fork.none', _('Repository forking disabled')), | |
1536 ('hg.fork.repository', _('Repository forking enabled')), | |
1537 | |
1538 ('hg.register.none', _('Registration disabled')), | |
1539 ('hg.register.manual_activate', _('User Registration with manual account activation')), | |
1540 ('hg.register.auto_activate', _('User Registration with automatic account activation')), | |
1541 | |
1542 ('hg.extern_activate.manual', _('Manual activation of external account')), | |
1543 ('hg.extern_activate.auto', _('Automatic activation of external account')), | |
1544 | |
1545 ] | |
1546 | |
1547 #definition of system default permissions for DEFAULT user | |
1548 DEFAULT_USER_PERMISSIONS = [ | |
1549 'repository.read', | |
1550 'group.read', | |
1551 'usergroup.read', | |
1552 'hg.create.repository', | |
1553 'hg.fork.repository', | |
1554 'hg.register.manual_activate', | |
1555 'hg.extern_activate.auto', | |
1556 ] | |
1557 | |
1558 # defines which permissions are more important higher the more important | |
1559 # Weight defines which permissions are more important. | |
1560 # The higher number the more important. | |
1561 PERM_WEIGHTS = { | |
1562 'repository.none': 0, | |
1563 'repository.read': 1, | |
1564 'repository.write': 3, | |
1565 'repository.admin': 4, | |
1566 | |
1567 'group.none': 0, | |
1568 'group.read': 1, | |
1569 'group.write': 3, | |
1570 'group.admin': 4, | |
1571 | |
1572 'usergroup.none': 0, | |
1573 'usergroup.read': 1, | |
1574 'usergroup.write': 3, | |
1575 'usergroup.admin': 4, | |
1576 'hg.repogroup.create.false': 0, | |
1577 'hg.repogroup.create.true': 1, | |
1578 | |
1579 'hg.usergroup.create.false': 0, | |
1580 'hg.usergroup.create.true': 1, | |
1581 | |
1582 'hg.fork.none': 0, | |
1583 'hg.fork.repository': 1, | |
1584 'hg.create.none': 0, | |
1585 'hg.create.repository': 1 | |
1586 } | |
1587 | |
1588 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1589 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1590 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1591 | |
1592 def __unicode__(self): | |
1593 return u"<%s('%s:%s')>" % ( | |
1594 self.__class__.__name__, self.permission_id, self.permission_name | |
1595 ) | |
1596 | |
1597 @classmethod | |
1598 def get_by_key(cls, key): | |
1599 return cls.query().filter(cls.permission_name == key).scalar() | |
1600 | |
1601 @classmethod | |
1602 def get_default_perms(cls, default_user_id): | |
1603 q = Session().query(UserRepoToPerm, Repository, cls)\ | |
1604 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ | |
1605 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\ | |
1606 .filter(UserRepoToPerm.user_id == default_user_id) | |
1607 | |
1608 return q.all() | |
1609 | |
1610 @classmethod | |
1611 def get_default_group_perms(cls, default_user_id): | |
1612 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\ | |
1613 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\ | |
1614 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\ | |
1615 .filter(UserRepoGroupToPerm.user_id == default_user_id) | |
1616 | |
1617 return q.all() | |
1618 | |
1619 @classmethod | |
1620 def get_default_user_group_perms(cls, default_user_id): | |
1621 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\ | |
1622 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\ | |
1623 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\ | |
1624 .filter(UserUserGroupToPerm.user_id == default_user_id) | |
1625 | |
1626 return q.all() | |
1627 | |
1628 | |
1629 class UserRepoToPerm(Base, BaseModel): | |
1630 __tablename__ = 'repo_to_perm' | |
1631 __table_args__ = ( | |
1632 UniqueConstraint('user_id', 'repository_id', 'permission_id'), | |
1633 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1634 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1635 ) | |
1636 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1637 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1638 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1639 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |
1640 | |
1641 user = relationship('User') | |
1642 repository = relationship('Repository') | |
1643 permission = relationship('Permission') | |
1644 | |
1645 @classmethod | |
1646 def create(cls, user, repository, permission): | |
1647 n = cls() | |
1648 n.user = user | |
1649 n.repository = repository | |
1650 n.permission = permission | |
1651 Session().add(n) | |
1652 return n | |
1653 | |
1654 def __unicode__(self): | |
1655 return u'<%s => %s >' % (self.user, self.repository) | |
1656 | |
1657 | |
1658 class UserUserGroupToPerm(Base, BaseModel): | |
1659 __tablename__ = 'user_user_group_to_perm' | |
1660 __table_args__ = ( | |
1661 UniqueConstraint('user_id', 'user_group_id', 'permission_id'), | |
1662 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1663 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1664 ) | |
1665 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1666 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1667 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1668 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1669 | |
1670 user = relationship('User') | |
1671 user_group = relationship('UserGroup') | |
1672 permission = relationship('Permission') | |
1673 | |
1674 @classmethod | |
1675 def create(cls, user, user_group, permission): | |
1676 n = cls() | |
1677 n.user = user | |
1678 n.user_group = user_group | |
1679 n.permission = permission | |
1680 Session().add(n) | |
1681 return n | |
1682 | |
1683 def __unicode__(self): | |
1684 return u'<%s => %s >' % (self.user, self.user_group) | |
1685 | |
1686 | |
1687 class UserToPerm(Base, BaseModel): | |
1688 __tablename__ = 'user_to_perm' | |
1689 __table_args__ = ( | |
1690 UniqueConstraint('user_id', 'permission_id'), | |
1691 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1692 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1693 ) | |
1694 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1696 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1697 | |
1698 user = relationship('User') | |
1699 permission = relationship('Permission', lazy='joined') | |
1700 | |
1701 def __unicode__(self): | |
1702 return u'<%s => %s >' % (self.user, self.permission) | |
1703 | |
1704 | |
1705 class UserGroupRepoToPerm(Base, BaseModel): | |
1706 __tablename__ = 'users_group_repo_to_perm' | |
1707 __table_args__ = ( | |
1708 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), | |
1709 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1710 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1711 ) | |
1712 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1713 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1714 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None) | |
1716 | |
1717 users_group = relationship('UserGroup') | |
1718 permission = relationship('Permission') | |
1719 repository = relationship('Repository') | |
1720 | |
1721 @classmethod | |
1722 def create(cls, users_group, repository, permission): | |
1723 n = cls() | |
1724 n.users_group = users_group | |
1725 n.repository = repository | |
1726 n.permission = permission | |
1727 Session().add(n) | |
1728 return n | |
1729 | |
1730 def __unicode__(self): | |
1731 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository) | |
1732 | |
1733 | |
1734 class UserGroupUserGroupToPerm(Base, BaseModel): | |
1735 __tablename__ = 'user_group_user_group_to_perm' | |
1736 __table_args__ = ( | |
1737 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'), | |
1738 CheckConstraint('target_user_group_id != user_group_id'), | |
1739 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1740 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1741 ) | |
1742 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) | |
1743 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1744 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1745 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1746 | |
1747 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id') | |
1748 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id') | |
1749 permission = relationship('Permission') | |
1750 | |
1751 @classmethod | |
1752 def create(cls, target_user_group, user_group, permission): | |
1753 n = cls() | |
1754 n.target_user_group = target_user_group | |
1755 n.user_group = user_group | |
1756 n.permission = permission | |
1757 Session().add(n) | |
1758 return n | |
1759 | |
1760 def __unicode__(self): | |
1761 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group) | |
1762 | |
1763 | |
1764 class UserGroupToPerm(Base, BaseModel): | |
1765 __tablename__ = 'users_group_to_perm' | |
1766 __table_args__ = ( | |
1767 UniqueConstraint('users_group_id', 'permission_id',), | |
1768 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1770 ) | |
1771 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1772 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1773 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1774 | |
1775 users_group = relationship('UserGroup') | |
1776 permission = relationship('Permission') | |
1777 | |
1778 | |
1779 class UserRepoGroupToPerm(Base, BaseModel): | |
1780 __tablename__ = 'user_repo_group_to_perm' | |
1781 __table_args__ = ( | |
1782 UniqueConstraint('user_id', 'group_id', 'permission_id'), | |
1783 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1784 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1785 ) | |
1786 | |
1787 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1788 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1789 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) | |
1790 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1791 | |
1792 user = relationship('User') | |
1793 group = relationship('RepoGroup') | |
1794 permission = relationship('Permission') | |
1795 | |
1796 | |
1797 class UserGroupRepoGroupToPerm(Base, BaseModel): | |
1798 __tablename__ = 'users_group_repo_group_to_perm' | |
1799 __table_args__ = ( | |
1800 UniqueConstraint('users_group_id', 'group_id'), | |
1801 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1803 ) | |
1804 | |
1805 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) | |
1806 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None) | |
1807 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None) | |
1808 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None) | |
1809 | |
1810 users_group = relationship('UserGroup') | |
1811 permission = relationship('Permission') | |
1812 group = relationship('RepoGroup') | |
1813 | |
1814 | |
1815 class Statistics(Base, BaseModel): | |
1816 __tablename__ = 'statistics' | |
1817 __table_args__ = ( | |
1818 UniqueConstraint('repository_id'), | |
1819 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1820 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1821 ) | |
1822 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1823 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None) | |
1824 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False) | |
1825 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data | |
1826 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data | |
1827 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data | |
1828 | |
1829 repository = relationship('Repository', single_parent=True) | |
1830 | |
1831 | |
1832 class UserFollowing(Base, BaseModel): | |
1833 __tablename__ = 'user_followings' | |
1834 __table_args__ = ( | |
1835 UniqueConstraint('user_id', 'follows_repository_id'), | |
1836 UniqueConstraint('user_id', 'follows_user_id'), | |
1837 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1838 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
1839 ) | |
1840 | |
1841 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1842 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None) | |
1843 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None) | |
1844 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |
1845 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
1846 | |
1847 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id') | |
1848 | |
1849 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id') | |
1850 follows_repository = relationship('Repository', order_by='Repository.repo_name') | |
1851 | |
1852 @classmethod | |
1853 def get_repo_followers(cls, repo_id): | |
1854 return cls.query().filter(cls.follows_repo_id == repo_id) | |
1855 | |
1856 | |
1857 class CacheInvalidation(Base, BaseModel): | |
1858 __tablename__ = 'cache_invalidation' | |
1859 __table_args__ = ( | |
1860 UniqueConstraint('cache_key'), | |
1861 Index('key_idx', 'cache_key'), | |
1862 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1863 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
1864 ) | |
1865 # cache_id, not used | |
1866 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
1867 # cache_key as created by _get_cache_key | |
1868 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1869 # cache_args is a repo_name | |
1870 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
1871 # instance sets cache_active True when it is caching, | |
1872 # other instances set cache_active to False to indicate that this cache is invalid | |
1873 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False) | |
1874 | |
1875 def __init__(self, cache_key, repo_name=''): | |
1876 self.cache_key = cache_key | |
1877 self.cache_args = repo_name | |
1878 self.cache_active = False | |
1879 | |
1880 def __unicode__(self): | |
1881 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__, | |
1882 self.cache_id, self.cache_key, self.cache_active) | |
1883 | |
1884 def _cache_key_partition(self): | |
1885 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args) | |
1886 return prefix, repo_name, suffix | |
1887 | |
1888 def get_prefix(self): | |
1889 """ | |
1890 get prefix that might have been used in _get_cache_key to | |
1891 generate self.cache_key. Only used for informational purposes | |
1892 in repo_edit.html. | |
1893 """ | |
1894 # prefix, repo_name, suffix | |
1895 return self._cache_key_partition()[0] | |
1896 | |
1897 def get_suffix(self): | |
1898 """ | |
1899 get suffix that might have been used in _get_cache_key to | |
1900 generate self.cache_key. Only used for informational purposes | |
1901 in repo_edit.html. | |
1902 """ | |
1903 # prefix, repo_name, suffix | |
1904 return self._cache_key_partition()[2] | |
1905 | |
1906 @classmethod | |
1907 def clear_cache(cls): | |
1908 """ | |
1909 Delete all cache keys from database. | |
1910 Should only be run when all instances are down and all entries thus stale. | |
1911 """ | |
1912 cls.query().delete() | |
1913 Session().commit() | |
1914 | |
1915 @classmethod | |
1916 def _get_cache_key(cls, key): | |
1917 """ | |
1918 Wrapper for generating a unique cache key for this instance and "key". | |
1919 key must / will start with a repo_name which will be stored in .cache_args . | |
1920 """ | |
1921 import rhodecode | |
1922 prefix = rhodecode.CONFIG.get('instance_id', '') | |
1923 return "%s%s" % (prefix, key) | |
1924 | |
1925 @classmethod | |
1926 def set_invalidate(cls, repo_name, delete=False): | |
1927 """ | |
1928 Mark all caches of a repo as invalid in the database. | |
1929 """ | |
1930 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all() | |
1931 | |
1932 try: | |
1933 for inv_obj in inv_objs: | |
1934 log.debug('marking %s key for invalidation based on repo_name=%s' | |
1935 % (inv_obj, safe_str(repo_name))) | |
1936 if delete: | |
1937 Session().delete(inv_obj) | |
1938 else: | |
1939 inv_obj.cache_active = False | |
1940 Session().add(inv_obj) | |
1941 Session().commit() | |
1942 except Exception: | |
1943 log.error(traceback.format_exc()) | |
1944 Session().rollback() | |
1945 | |
1946 @classmethod | |
1947 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None): | |
1948 """ | |
1949 Mark this cache key as active and currently cached. | |
1950 Return True if the existing cache registration still was valid. | |
1951 Return False to indicate that it had been invalidated and caches should be refreshed. | |
1952 """ | |
1953 | |
1954 key = (repo_name + '_' + kind) if kind else repo_name | |
1955 cache_key = cls._get_cache_key(key) | |
1956 | |
1957 if valid_cache_keys and cache_key in valid_cache_keys: | |
1958 return True | |
1959 | |
1960 try: | |
1961 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar() | |
1962 if not inv_obj: | |
1963 inv_obj = CacheInvalidation(cache_key, repo_name) | |
1964 was_valid = inv_obj.cache_active | |
1965 inv_obj.cache_active = True | |
1966 Session().add(inv_obj) | |
1967 Session().commit() | |
1968 return was_valid | |
1969 except Exception: | |
1970 log.error(traceback.format_exc()) | |
1971 Session().rollback() | |
1972 return False | |
1973 | |
1974 @classmethod | |
1975 def get_valid_cache_keys(cls): | |
1976 """ | |
1977 Return opaque object with information of which caches still are valid | |
1978 and can be used without checking for invalidation. | |
1979 """ | |
1980 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all()) | |
1981 | |
1982 | |
1983 class ChangesetComment(Base, BaseModel): | |
1984 __tablename__ = 'changeset_comments' | |
1985 __table_args__ = ( | |
1986 Index('cc_revision_idx', 'revision'), | |
1987 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
1988 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
1989 ) | |
1990 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True) | |
1991 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
1992 revision = Column('revision', String(40), nullable=True) | |
1993 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) | |
1994 line_no = Column('line_no', Unicode(10), nullable=True) | |
1995 hl_lines = Column('hl_lines', Unicode(512), nullable=True) | |
1996 f_path = Column('f_path', Unicode(1000), nullable=True) | |
1997 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) | |
1998 text = Column('text', UnicodeText(25000), nullable=False) | |
1999 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2000 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2001 | |
2002 author = relationship('User', lazy='joined') | |
2003 repo = relationship('Repository') | |
2004 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan") | |
2005 pull_request = relationship('PullRequest', lazy='joined') | |
2006 | |
2007 @classmethod | |
2008 def get_users(cls, revision=None, pull_request_id=None): | |
2009 """ | |
2010 Returns user associated with this ChangesetComment. ie those | |
2011 who actually commented | |
2012 | |
2013 :param cls: | |
2014 :param revision: | |
2015 """ | |
2016 q = Session().query(User)\ | |
2017 .join(ChangesetComment.author) | |
2018 if revision: | |
2019 q = q.filter(cls.revision == revision) | |
2020 elif pull_request_id: | |
2021 q = q.filter(cls.pull_request_id == pull_request_id) | |
2022 return q.all() | |
2023 | |
2024 | |
2025 class ChangesetStatus(Base, BaseModel): | |
2026 __tablename__ = 'changeset_statuses' | |
2027 __table_args__ = ( | |
2028 Index('cs_revision_idx', 'revision'), | |
2029 Index('cs_version_idx', 'version'), | |
2030 UniqueConstraint('repo_id', 'revision', 'version'), | |
2031 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2032 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
2033 ) | |
2034 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed' | |
2035 STATUS_APPROVED = 'approved' | |
2036 STATUS_REJECTED = 'rejected' | |
2037 STATUS_UNDER_REVIEW = 'under_review' | |
2038 | |
2039 STATUSES = [ | |
2040 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default | |
2041 (STATUS_APPROVED, _("Approved")), | |
2042 (STATUS_REJECTED, _("Rejected")), | |
2043 (STATUS_UNDER_REVIEW, _("Under Review")), | |
2044 ] | |
2045 | |
2046 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True) | |
2047 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
2048 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) | |
2049 revision = Column('revision', String(40), nullable=False) | |
2050 status = Column('status', String(128), nullable=False, default=DEFAULT) | |
2051 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id')) | |
2052 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) | |
2053 version = Column('version', Integer(), nullable=False, default=0) | |
2054 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True) | |
2055 | |
2056 author = relationship('User', lazy='joined') | |
2057 repo = relationship('Repository') | |
2058 comment = relationship('ChangesetComment', lazy='joined') | |
2059 pull_request = relationship('PullRequest', lazy='joined') | |
2060 | |
2061 def __unicode__(self): | |
2062 return u"<%s('%s:%s')>" % ( | |
2063 self.__class__.__name__, | |
2064 self.status, self.author | |
2065 ) | |
2066 | |
2067 @classmethod | |
2068 def get_status_lbl(cls, value): | |
2069 return dict(cls.STATUSES).get(value) | |
2070 | |
2071 @property | |
2072 def status_lbl(self): | |
2073 return ChangesetStatus.get_status_lbl(self.status) | |
2074 | |
2075 | |
2076 class PullRequest(Base, BaseModel): | |
2077 __tablename__ = 'pull_requests' | |
2078 __table_args__ = ( | |
2079 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2080 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
2081 ) | |
2082 | |
2083 # values for .status | |
2084 STATUS_NEW = u'new' | |
2085 STATUS_OPEN = u'open' | |
2086 STATUS_CLOSED = u'closed' | |
2087 | |
2088 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True) | |
2089 title = Column('title', Unicode(256), nullable=True) | |
2090 description = Column('description', UnicodeText(10240), nullable=True) | |
2091 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW) # only for closedness, not approve/reject/etc | |
2092 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2093 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2094 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None) | |
2095 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max | |
2096 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
2097 org_ref = Column('org_ref', Unicode(256), nullable=False) | |
2098 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False) | |
2099 other_ref = Column('other_ref', Unicode(256), nullable=False) | |
2100 | |
2101 @hybrid_property | |
2102 def revisions(self): | |
2103 return self._revisions.split(':') | |
2104 | |
2105 @revisions.setter | |
2106 def revisions(self, val): | |
2107 self._revisions = ':'.join(val) | |
2108 | |
2109 @property | |
2110 def org_ref_parts(self): | |
2111 return self.org_ref.split(':') | |
2112 | |
2113 @property | |
2114 def other_ref_parts(self): | |
2115 return self.other_ref.split(':') | |
2116 | |
2117 author = relationship('User', lazy='joined') | |
2118 reviewers = relationship('PullRequestReviewers', | |
2119 cascade="all, delete, delete-orphan") | |
2120 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id') | |
2121 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id') | |
2122 statuses = relationship('ChangesetStatus') | |
2123 comments = relationship('ChangesetComment', | |
2124 cascade="all, delete, delete-orphan") | |
2125 | |
2126 def is_closed(self): | |
2127 return self.status == self.STATUS_CLOSED | |
2128 | |
2129 @property | |
2130 def last_review_status(self): | |
2131 return self.statuses[-1].status if self.statuses else '' | |
2132 | |
2133 def __json__(self): | |
2134 return dict( | |
2135 revisions=self.revisions | |
2136 ) | |
2137 | |
2138 | |
2139 class PullRequestReviewers(Base, BaseModel): | |
2140 __tablename__ = 'pull_request_reviewers' | |
2141 __table_args__ = ( | |
2142 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2143 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
2144 ) | |
2145 | |
2146 def __init__(self, user=None, pull_request=None): | |
2147 self.user = user | |
2148 self.pull_request = pull_request | |
2149 | |
2150 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True) | |
2151 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False) | |
2152 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True) | |
2153 | |
2154 user = relationship('User') | |
2155 pull_request = relationship('PullRequest') | |
2156 | |
2157 | |
2158 class Notification(Base, BaseModel): | |
2159 __tablename__ = 'notifications' | |
2160 __table_args__ = ( | |
2161 Index('notification_type_idx', 'type'), | |
2162 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2163 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
2164 ) | |
2165 | |
2166 TYPE_CHANGESET_COMMENT = u'cs_comment' | |
2167 TYPE_MESSAGE = u'message' | |
2168 TYPE_MENTION = u'mention' | |
2169 TYPE_REGISTRATION = u'registration' | |
2170 TYPE_PULL_REQUEST = u'pull_request' | |
2171 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment' | |
2172 | |
2173 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True) | |
2174 subject = Column('subject', Unicode(512), nullable=True) | |
2175 body = Column('body', UnicodeText(50000), nullable=True) | |
2176 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True) | |
2177 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2178 type_ = Column('type', Unicode(256)) | |
2179 | |
2180 created_by_user = relationship('User') | |
2181 notifications_to_users = relationship('UserNotification', lazy='joined', | |
2182 cascade="all, delete, delete-orphan") | |
2183 | |
2184 @property | |
2185 def recipients(self): | |
2186 return [x.user for x in UserNotification.query()\ | |
2187 .filter(UserNotification.notification == self)\ | |
2188 .order_by(UserNotification.user_id.asc()).all()] | |
2189 | |
2190 @classmethod | |
2191 def create(cls, created_by, subject, body, recipients, type_=None): | |
2192 if type_ is None: | |
2193 type_ = Notification.TYPE_MESSAGE | |
2194 | |
2195 notification = cls() | |
2196 notification.created_by_user = created_by | |
2197 notification.subject = subject | |
2198 notification.body = body | |
2199 notification.type_ = type_ | |
2200 notification.created_on = datetime.datetime.now() | |
2201 | |
2202 for u in recipients: | |
2203 assoc = UserNotification() | |
2204 assoc.notification = notification | |
2205 u.notifications.append(assoc) | |
2206 Session().add(notification) | |
2207 return notification | |
2208 | |
2209 @property | |
2210 def description(self): | |
2211 from rhodecode.model.notification import NotificationModel | |
2212 return NotificationModel().make_description(self) | |
2213 | |
2214 | |
2215 class UserNotification(Base, BaseModel): | |
2216 __tablename__ = 'user_to_notification' | |
2217 __table_args__ = ( | |
2218 UniqueConstraint('user_id', 'notification_id'), | |
2219 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2220 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
2221 ) | |
2222 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) | |
2223 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True) | |
2224 read = Column('read', Boolean, default=False) | |
2225 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None) | |
2226 | |
2227 user = relationship('User', lazy="joined") | |
2228 notification = relationship('Notification', lazy="joined", | |
2229 order_by=lambda: Notification.created_on.desc(),) | |
2230 | |
2231 def mark_as_read(self): | |
2232 self.read = True | |
2233 Session().add(self) | |
2234 | |
2235 | |
2236 class Gist(Base, BaseModel): | |
2237 __tablename__ = 'gists' | |
2238 __table_args__ = ( | |
2239 Index('g_gist_access_id_idx', 'gist_access_id'), | |
2240 Index('g_created_on_idx', 'created_on'), | |
2241 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2242 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
2243 ) | |
2244 GIST_PUBLIC = u'public' | |
2245 GIST_PRIVATE = u'private' | |
2246 | |
2247 gist_id = Column('gist_id', Integer(), primary_key=True) | |
2248 gist_access_id = Column('gist_access_id', Unicode(250)) | |
2249 gist_description = Column('gist_description', UnicodeText(1024)) | |
2250 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True) | |
2251 gist_expires = Column('gist_expires', Float(53), nullable=False) | |
2252 gist_type = Column('gist_type', Unicode(128), nullable=False) | |
2253 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2254 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
2255 | |
2256 owner = relationship('User') | |
2257 | |
2258 @classmethod | |
2259 def get_or_404(cls, id_): | |
2260 res = cls.query().filter(cls.gist_access_id == id_).scalar() | |
2261 if not res: | |
2262 raise HTTPNotFound | |
2263 return res | |
2264 | |
2265 @classmethod | |
2266 def get_by_access_id(cls, gist_access_id): | |
2267 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() | |
2268 | |
2269 def gist_url(self): | |
2270 import rhodecode | |
2271 alias_url = rhodecode.CONFIG.get('gist_alias_url') | |
2272 if alias_url: | |
2273 return alias_url.replace('{gistid}', self.gist_access_id) | |
2274 | |
2275 from pylons import url | |
2276 return url('gist', gist_id=self.gist_access_id, qualified=True) | |
2277 | |
2278 @classmethod | |
2279 def base_path(cls): | |
2280 """ | |
2281 Returns base path when all gists are stored | |
2282 | |
2283 :param cls: | |
2284 """ | |
2285 from rhodecode.model.gist import GIST_STORE_LOC | |
2286 q = Session().query(RhodeCodeUi)\ | |
2287 .filter(RhodeCodeUi.ui_key == URL_SEP) | |
2288 q = q.options(FromCache("sql_cache_short", "repository_repo_path")) | |
2289 return os.path.join(q.one().ui_value, GIST_STORE_LOC) | |
2290 | |
2291 def get_api_data(self): | |
2292 """ | |
2293 Common function for generating gist related data for API | |
2294 """ | |
2295 gist = self | |
2296 data = dict( | |
2297 gist_id=gist.gist_id, | |
2298 type=gist.gist_type, | |
2299 access_id=gist.gist_access_id, | |
2300 description=gist.gist_description, | |
2301 url=gist.gist_url(), | |
2302 expires=gist.gist_expires, | |
2303 created_on=gist.created_on, | |
2304 ) | |
2305 return data | |
2306 | |
2307 def __json__(self): | |
2308 data = dict( | |
2309 ) | |
2310 data.update(self.get_api_data()) | |
2311 return data | |
2312 ## SCM functions | |
2313 | |
2314 @property | |
2315 def scm_instance(self): | |
2316 from rhodecode.lib.vcs import get_repo | |
2317 base_path = self.base_path() | |
2318 return get_repo(os.path.join(*map(safe_str, | |
2319 [base_path, self.gist_access_id]))) | |
2320 | |
2321 | |
2322 class DbMigrateVersion(Base, BaseModel): | |
2323 __tablename__ = 'db_migrate_version' | |
2324 __table_args__ = ( | |
2325 {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
2326 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}, | |
2327 ) | |
2328 repository_id = Column('repository_id', String(250), primary_key=True) | |
2329 repository_path = Column('repository_path', Text) | |
2330 version = Column('version', Integer) |