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