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