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