##// END OF EJS Templates
users: change last activity to re-use new column.
marcink -
r1546:b45c2eaa default
parent child Browse files
Show More
@@ -1,239 +1,238 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils import PartialRenderer
30 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import safe_int, safe_unicode
31 from rhodecode.lib.utils2 import safe_int, safe_unicode
32 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.db import User, or_
33 from rhodecode.model.db import User, or_
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class AdminUsersView(BaseAppView):
39 class AdminUsersView(BaseAppView):
40 ALLOW_SCOPED_TOKENS = False
40 ALLOW_SCOPED_TOKENS = False
41 """
41 """
42 This view has alternative version inside EE, if modified please take a look
42 This view has alternative version inside EE, if modified please take a look
43 in there as well.
43 in there as well.
44 """
44 """
45
45
46 def load_default_context(self):
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
47 c = self._get_local_tmpl_context()
48 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
48 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
49 self._register_global_c(c)
49 self._register_global_c(c)
50 return c
50 return c
51
51
52 def _redirect_for_default_user(self, username):
52 def _redirect_for_default_user(self, username):
53 _ = self.request.translate
53 _ = self.request.translate
54 if username == User.DEFAULT_USER:
54 if username == User.DEFAULT_USER:
55 h.flash(_("You can't edit this user"), category='warning')
55 h.flash(_("You can't edit this user"), category='warning')
56 # TODO(marcink): redirect to 'users' admin panel once this
56 # TODO(marcink): redirect to 'users' admin panel once this
57 # is a pyramid view
57 # is a pyramid view
58 raise HTTPFound('/')
58 raise HTTPFound('/')
59
59
60 def _extract_ordering(self, request):
60 def _extract_ordering(self, request):
61 column_index = safe_int(request.GET.get('order[0][column]'))
61 column_index = safe_int(request.GET.get('order[0][column]'))
62 order_dir = request.GET.get(
62 order_dir = request.GET.get(
63 'order[0][dir]', 'desc')
63 'order[0][dir]', 'desc')
64 order_by = request.GET.get(
64 order_by = request.GET.get(
65 'columns[%s][data][sort]' % column_index, 'name_raw')
65 'columns[%s][data][sort]' % column_index, 'name_raw')
66
66
67 # translate datatable to DB columns
67 # translate datatable to DB columns
68 order_by = {
68 order_by = {
69 'first_name': 'name',
69 'first_name': 'name',
70 'last_name': 'lastname',
70 'last_name': 'lastname',
71 'last_activity': ''
71 'last_activity': ''
72 }.get(order_by) or order_by
72 }.get(order_by) or order_by
73
73
74 search_q = request.GET.get('search[value]')
74 search_q = request.GET.get('search[value]')
75 return search_q, order_by, order_dir
75 return search_q, order_by, order_dir
76
76
77 def _extract_chunk(self, request):
77 def _extract_chunk(self, request):
78 start = safe_int(request.GET.get('start'), 0)
78 start = safe_int(request.GET.get('start'), 0)
79 length = safe_int(request.GET.get('length'), 25)
79 length = safe_int(request.GET.get('length'), 25)
80 draw = safe_int(request.GET.get('draw'))
80 draw = safe_int(request.GET.get('draw'))
81 return draw, start, length
81 return draw, start, length
82
82
83 @HasPermissionAllDecorator('hg.admin')
83 @HasPermissionAllDecorator('hg.admin')
84 @view_config(
84 @view_config(
85 route_name='users', request_method='GET',
85 route_name='users', request_method='GET',
86 renderer='rhodecode:templates/admin/users/users.mako')
86 renderer='rhodecode:templates/admin/users/users.mako')
87 def users_list(self):
87 def users_list(self):
88 c = self.load_default_context()
88 c = self.load_default_context()
89 return self._get_template_context(c)
89 return self._get_template_context(c)
90
90
91 @HasPermissionAllDecorator('hg.admin')
91 @HasPermissionAllDecorator('hg.admin')
92 @view_config(
92 @view_config(
93 # renderer defined below
93 # renderer defined below
94 route_name='users_data', request_method='GET', renderer='json',
94 route_name='users_data', request_method='GET', renderer='json',
95 xhr=True)
95 xhr=True)
96 def users_list_data(self):
96 def users_list_data(self):
97 draw, start, limit = self._extract_chunk(self.request)
97 draw, start, limit = self._extract_chunk(self.request)
98 search_q, order_by, order_dir = self._extract_ordering(self.request)
98 search_q, order_by, order_dir = self._extract_ordering(self.request)
99
99
100 _render = PartialRenderer('data_table/_dt_elements.mako')
100 _render = PartialRenderer('data_table/_dt_elements.mako')
101
101
102 def user_actions(user_id, username):
102 def user_actions(user_id, username):
103 return _render("user_actions", user_id, username)
103 return _render("user_actions", user_id, username)
104
104
105 users_data_total_count = User.query()\
105 users_data_total_count = User.query()\
106 .filter(User.username != User.DEFAULT_USER) \
106 .filter(User.username != User.DEFAULT_USER) \
107 .count()
107 .count()
108
108
109 # json generate
109 # json generate
110 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 base_q = User.query().filter(User.username != User.DEFAULT_USER)
111
111
112 if search_q:
112 if search_q:
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 base_q = base_q.filter(or_(
114 base_q = base_q.filter(or_(
115 User.username.ilike(like_expression),
115 User.username.ilike(like_expression),
116 User._email.ilike(like_expression),
116 User._email.ilike(like_expression),
117 User.name.ilike(like_expression),
117 User.name.ilike(like_expression),
118 User.lastname.ilike(like_expression),
118 User.lastname.ilike(like_expression),
119 ))
119 ))
120
120
121 users_data_total_filtered_count = base_q.count()
121 users_data_total_filtered_count = base_q.count()
122
122
123 sort_col = getattr(User, order_by, None)
123 sort_col = getattr(User, order_by, None)
124 if sort_col and order_dir == 'asc':
124 if sort_col and order_dir == 'asc':
125 base_q = base_q.order_by(sort_col.asc())
125 base_q = base_q.order_by(sort_col.asc())
126 elif sort_col:
126 elif sort_col:
127 base_q = base_q.order_by(sort_col.desc())
127 base_q = base_q.order_by(sort_col.desc())
128
128
129 base_q = base_q.offset(start).limit(limit)
129 base_q = base_q.offset(start).limit(limit)
130 users_list = base_q.all()
130 users_list = base_q.all()
131
131
132 users_data = []
132 users_data = []
133 for user in users_list:
133 for user in users_list:
134 users_data.append({
134 users_data.append({
135 "username": h.gravatar_with_user(user.username),
135 "username": h.gravatar_with_user(user.username),
136 "email": user.email,
136 "email": user.email,
137 "first_name": h.escape(user.name),
137 "first_name": h.escape(user.name),
138 "last_name": h.escape(user.lastname),
138 "last_name": h.escape(user.lastname),
139 "last_login": h.format_date(user.last_login),
139 "last_login": h.format_date(user.last_login),
140 "last_activity": h.format_date(
140 "last_activity": h.format_date(user.last_activity),
141 h.time_to_datetime(user.user_data.get('last_activity', 0))),
142 "active": h.bool2icon(user.active),
141 "active": h.bool2icon(user.active),
143 "active_raw": user.active,
142 "active_raw": user.active,
144 "admin": h.bool2icon(user.admin),
143 "admin": h.bool2icon(user.admin),
145 "extern_type": user.extern_type,
144 "extern_type": user.extern_type,
146 "extern_name": user.extern_name,
145 "extern_name": user.extern_name,
147 "action": user_actions(user.user_id, user.username),
146 "action": user_actions(user.user_id, user.username),
148 })
147 })
149
148
150 data = ({
149 data = ({
151 'draw': draw,
150 'draw': draw,
152 'data': users_data,
151 'data': users_data,
153 'recordsTotal': users_data_total_count,
152 'recordsTotal': users_data_total_count,
154 'recordsFiltered': users_data_total_filtered_count,
153 'recordsFiltered': users_data_total_filtered_count,
155 })
154 })
156
155
157 return data
156 return data
158
157
159 @LoginRequired()
158 @LoginRequired()
160 @HasPermissionAllDecorator('hg.admin')
159 @HasPermissionAllDecorator('hg.admin')
161 @view_config(
160 @view_config(
162 route_name='edit_user_auth_tokens', request_method='GET',
161 route_name='edit_user_auth_tokens', request_method='GET',
163 renderer='rhodecode:templates/admin/users/user_edit.mako')
162 renderer='rhodecode:templates/admin/users/user_edit.mako')
164 def auth_tokens(self):
163 def auth_tokens(self):
165 _ = self.request.translate
164 _ = self.request.translate
166 c = self.load_default_context()
165 c = self.load_default_context()
167
166
168 user_id = self.request.matchdict.get('user_id')
167 user_id = self.request.matchdict.get('user_id')
169 c.user = User.get_or_404(user_id, pyramid_exc=True)
168 c.user = User.get_or_404(user_id, pyramid_exc=True)
170 self._redirect_for_default_user(c.user.username)
169 self._redirect_for_default_user(c.user.username)
171
170
172 c.active = 'auth_tokens'
171 c.active = 'auth_tokens'
173
172
174 c.lifetime_values = [
173 c.lifetime_values = [
175 (str(-1), _('forever')),
174 (str(-1), _('forever')),
176 (str(5), _('5 minutes')),
175 (str(5), _('5 minutes')),
177 (str(60), _('1 hour')),
176 (str(60), _('1 hour')),
178 (str(60 * 24), _('1 day')),
177 (str(60 * 24), _('1 day')),
179 (str(60 * 24 * 30), _('1 month')),
178 (str(60 * 24 * 30), _('1 month')),
180 ]
179 ]
181 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
180 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
182 c.role_values = [
181 c.role_values = [
183 (x, AuthTokenModel.cls._get_role_name(x))
182 (x, AuthTokenModel.cls._get_role_name(x))
184 for x in AuthTokenModel.cls.ROLES]
183 for x in AuthTokenModel.cls.ROLES]
185 c.role_options = [(c.role_values, _("Role"))]
184 c.role_options = [(c.role_values, _("Role"))]
186 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
185 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
187 c.user.user_id, show_expired=True)
186 c.user.user_id, show_expired=True)
188 return self._get_template_context(c)
187 return self._get_template_context(c)
189
188
190 def maybe_attach_token_scope(self, token):
189 def maybe_attach_token_scope(self, token):
191 # implemented in EE edition
190 # implemented in EE edition
192 pass
191 pass
193
192
194 @LoginRequired()
193 @LoginRequired()
195 @HasPermissionAllDecorator('hg.admin')
194 @HasPermissionAllDecorator('hg.admin')
196 @CSRFRequired()
195 @CSRFRequired()
197 @view_config(
196 @view_config(
198 route_name='edit_user_auth_tokens_add', request_method='POST')
197 route_name='edit_user_auth_tokens_add', request_method='POST')
199 def auth_tokens_add(self):
198 def auth_tokens_add(self):
200 _ = self.request.translate
199 _ = self.request.translate
201 c = self.load_default_context()
200 c = self.load_default_context()
202
201
203 user_id = self.request.matchdict.get('user_id')
202 user_id = self.request.matchdict.get('user_id')
204 c.user = User.get_or_404(user_id, pyramid_exc=True)
203 c.user = User.get_or_404(user_id, pyramid_exc=True)
205 self._redirect_for_default_user(c.user.username)
204 self._redirect_for_default_user(c.user.username)
206
205
207 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
206 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
208 description = self.request.POST.get('description')
207 description = self.request.POST.get('description')
209 role = self.request.POST.get('role')
208 role = self.request.POST.get('role')
210
209
211 token = AuthTokenModel().create(
210 token = AuthTokenModel().create(
212 c.user.user_id, description, lifetime, role)
211 c.user.user_id, description, lifetime, role)
213 self.maybe_attach_token_scope(token)
212 self.maybe_attach_token_scope(token)
214 Session().commit()
213 Session().commit()
215
214
216 h.flash(_("Auth token successfully created"), category='success')
215 h.flash(_("Auth token successfully created"), category='success')
217 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
216 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
218
217
219 @LoginRequired()
218 @LoginRequired()
220 @HasPermissionAllDecorator('hg.admin')
219 @HasPermissionAllDecorator('hg.admin')
221 @CSRFRequired()
220 @CSRFRequired()
222 @view_config(
221 @view_config(
223 route_name='edit_user_auth_tokens_delete', request_method='POST')
222 route_name='edit_user_auth_tokens_delete', request_method='POST')
224 def auth_tokens_delete(self):
223 def auth_tokens_delete(self):
225 _ = self.request.translate
224 _ = self.request.translate
226 c = self.load_default_context()
225 c = self.load_default_context()
227
226
228 user_id = self.request.matchdict.get('user_id')
227 user_id = self.request.matchdict.get('user_id')
229 c.user = User.get_or_404(user_id, pyramid_exc=True)
228 c.user = User.get_or_404(user_id, pyramid_exc=True)
230 self._redirect_for_default_user(c.user.username)
229 self._redirect_for_default_user(c.user.username)
231
230
232 del_auth_token = self.request.POST.get('del_auth_token')
231 del_auth_token = self.request.POST.get('del_auth_token')
233
232
234 if del_auth_token:
233 if del_auth_token:
235 AuthTokenModel().delete(del_auth_token, c.user.user_id)
234 AuthTokenModel().delete(del_auth_token, c.user.user_id)
236 Session().commit()
235 Session().commit()
237 h.flash(_("Auth token successfully deleted"), category='success')
236 h.flash(_("Auth token successfully deleted"), category='success')
238
237
239 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
238 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
@@ -1,3970 +1,3967 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from beaker.cache import cache_region
44 from beaker.cache import cache_region
45 from zope.cachedescriptors.property import Lazy as LazyProperty
45 from zope.cachedescriptors.property import Lazy as LazyProperty
46
46
47 from pylons import url
47 from pylons import url
48 from pylons.i18n.translation import lazy_ugettext as _
48 from pylons.i18n.translation import lazy_ugettext as _
49
49
50 from rhodecode.lib.vcs import get_vcs_instance
50 from rhodecode.lib.vcs import get_vcs_instance
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
51 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
52 from rhodecode.lib.utils2 import (
52 from rhodecode.lib.utils2 import (
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
53 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
54 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
55 glob2re, StrictAttributeDict, cleaned_uri)
55 glob2re, StrictAttributeDict, cleaned_uri)
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
56 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
57 from rhodecode.lib.ext_json import json
57 from rhodecode.lib.ext_json import json
58 from rhodecode.lib.caching_query import FromCache
58 from rhodecode.lib.caching_query import FromCache
59 from rhodecode.lib.encrypt import AESCipher
59 from rhodecode.lib.encrypt import AESCipher
60
60
61 from rhodecode.model.meta import Base, Session
61 from rhodecode.model.meta import Base, Session
62
62
63 URL_SEP = '/'
63 URL_SEP = '/'
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66 # =============================================================================
66 # =============================================================================
67 # BASE CLASSES
67 # BASE CLASSES
68 # =============================================================================
68 # =============================================================================
69
69
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
70 # this is propagated from .ini file rhodecode.encrypted_values.secret or
71 # beaker.session.secret if first is not set.
71 # beaker.session.secret if first is not set.
72 # and initialized at environment.py
72 # and initialized at environment.py
73 ENCRYPTION_KEY = None
73 ENCRYPTION_KEY = None
74
74
75 # used to sort permissions by types, '#' used here is not allowed to be in
75 # used to sort permissions by types, '#' used here is not allowed to be in
76 # usernames, and it's very early in sorted string.printable table.
76 # usernames, and it's very early in sorted string.printable table.
77 PERMISSION_TYPE_SORT = {
77 PERMISSION_TYPE_SORT = {
78 'admin': '####',
78 'admin': '####',
79 'write': '###',
79 'write': '###',
80 'read': '##',
80 'read': '##',
81 'none': '#',
81 'none': '#',
82 }
82 }
83
83
84
84
85 def display_sort(obj):
85 def display_sort(obj):
86 """
86 """
87 Sort function used to sort permissions in .permissions() function of
87 Sort function used to sort permissions in .permissions() function of
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
88 Repository, RepoGroup, UserGroup. Also it put the default user in front
89 of all other resources
89 of all other resources
90 """
90 """
91
91
92 if obj.username == User.DEFAULT_USER:
92 if obj.username == User.DEFAULT_USER:
93 return '#####'
93 return '#####'
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
94 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
95 return prefix + obj.username
95 return prefix + obj.username
96
96
97
97
98 def _hash_key(k):
98 def _hash_key(k):
99 return md5_safe(k)
99 return md5_safe(k)
100
100
101
101
102 class EncryptedTextValue(TypeDecorator):
102 class EncryptedTextValue(TypeDecorator):
103 """
103 """
104 Special column for encrypted long text data, use like::
104 Special column for encrypted long text data, use like::
105
105
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
106 value = Column("encrypted_value", EncryptedValue(), nullable=False)
107
107
108 This column is intelligent so if value is in unencrypted form it return
108 This column is intelligent so if value is in unencrypted form it return
109 unencrypted form, but on save it always encrypts
109 unencrypted form, but on save it always encrypts
110 """
110 """
111 impl = Text
111 impl = Text
112
112
113 def process_bind_param(self, value, dialect):
113 def process_bind_param(self, value, dialect):
114 if not value:
114 if not value:
115 return value
115 return value
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
116 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
117 # protect against double encrypting if someone manually starts
117 # protect against double encrypting if someone manually starts
118 # doing
118 # doing
119 raise ValueError('value needs to be in unencrypted format, ie. '
119 raise ValueError('value needs to be in unencrypted format, ie. '
120 'not starting with enc$aes')
120 'not starting with enc$aes')
121 return 'enc$aes_hmac$%s' % AESCipher(
121 return 'enc$aes_hmac$%s' % AESCipher(
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
122 ENCRYPTION_KEY, hmac=True).encrypt(value)
123
123
124 def process_result_value(self, value, dialect):
124 def process_result_value(self, value, dialect):
125 import rhodecode
125 import rhodecode
126
126
127 if not value:
127 if not value:
128 return value
128 return value
129
129
130 parts = value.split('$', 3)
130 parts = value.split('$', 3)
131 if not len(parts) == 3:
131 if not len(parts) == 3:
132 # probably not encrypted values
132 # probably not encrypted values
133 return value
133 return value
134 else:
134 else:
135 if parts[0] != 'enc':
135 if parts[0] != 'enc':
136 # parts ok but without our header ?
136 # parts ok but without our header ?
137 return value
137 return value
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
138 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
139 'rhodecode.encrypted_values.strict') or True)
139 'rhodecode.encrypted_values.strict') or True)
140 # at that stage we know it's our encryption
140 # at that stage we know it's our encryption
141 if parts[1] == 'aes':
141 if parts[1] == 'aes':
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
142 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
143 elif parts[1] == 'aes_hmac':
143 elif parts[1] == 'aes_hmac':
144 decrypted_data = AESCipher(
144 decrypted_data = AESCipher(
145 ENCRYPTION_KEY, hmac=True,
145 ENCRYPTION_KEY, hmac=True,
146 strict_verification=enc_strict_mode).decrypt(parts[2])
146 strict_verification=enc_strict_mode).decrypt(parts[2])
147 else:
147 else:
148 raise ValueError(
148 raise ValueError(
149 'Encryption type part is wrong, must be `aes` '
149 'Encryption type part is wrong, must be `aes` '
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
150 'or `aes_hmac`, got `%s` instead' % (parts[1]))
151 return decrypted_data
151 return decrypted_data
152
152
153
153
154 class BaseModel(object):
154 class BaseModel(object):
155 """
155 """
156 Base Model for all classes
156 Base Model for all classes
157 """
157 """
158
158
159 @classmethod
159 @classmethod
160 def _get_keys(cls):
160 def _get_keys(cls):
161 """return column names for this model """
161 """return column names for this model """
162 return class_mapper(cls).c.keys()
162 return class_mapper(cls).c.keys()
163
163
164 def get_dict(self):
164 def get_dict(self):
165 """
165 """
166 return dict with keys and values corresponding
166 return dict with keys and values corresponding
167 to this model data """
167 to this model data """
168
168
169 d = {}
169 d = {}
170 for k in self._get_keys():
170 for k in self._get_keys():
171 d[k] = getattr(self, k)
171 d[k] = getattr(self, k)
172
172
173 # also use __json__() if present to get additional fields
173 # also use __json__() if present to get additional fields
174 _json_attr = getattr(self, '__json__', None)
174 _json_attr = getattr(self, '__json__', None)
175 if _json_attr:
175 if _json_attr:
176 # update with attributes from __json__
176 # update with attributes from __json__
177 if callable(_json_attr):
177 if callable(_json_attr):
178 _json_attr = _json_attr()
178 _json_attr = _json_attr()
179 for k, val in _json_attr.iteritems():
179 for k, val in _json_attr.iteritems():
180 d[k] = val
180 d[k] = val
181 return d
181 return d
182
182
183 def get_appstruct(self):
183 def get_appstruct(self):
184 """return list with keys and values tuples corresponding
184 """return list with keys and values tuples corresponding
185 to this model data """
185 to this model data """
186
186
187 l = []
187 l = []
188 for k in self._get_keys():
188 for k in self._get_keys():
189 l.append((k, getattr(self, k),))
189 l.append((k, getattr(self, k),))
190 return l
190 return l
191
191
192 def populate_obj(self, populate_dict):
192 def populate_obj(self, populate_dict):
193 """populate model with data from given populate_dict"""
193 """populate model with data from given populate_dict"""
194
194
195 for k in self._get_keys():
195 for k in self._get_keys():
196 if k in populate_dict:
196 if k in populate_dict:
197 setattr(self, k, populate_dict[k])
197 setattr(self, k, populate_dict[k])
198
198
199 @classmethod
199 @classmethod
200 def query(cls):
200 def query(cls):
201 return Session().query(cls)
201 return Session().query(cls)
202
202
203 @classmethod
203 @classmethod
204 def get(cls, id_):
204 def get(cls, id_):
205 if id_:
205 if id_:
206 return cls.query().get(id_)
206 return cls.query().get(id_)
207
207
208 @classmethod
208 @classmethod
209 def get_or_404(cls, id_, pyramid_exc=False):
209 def get_or_404(cls, id_, pyramid_exc=False):
210 if pyramid_exc:
210 if pyramid_exc:
211 # NOTE(marcink): backward compat, once migration to pyramid
211 # NOTE(marcink): backward compat, once migration to pyramid
212 # this should only use pyramid exceptions
212 # this should only use pyramid exceptions
213 from pyramid.httpexceptions import HTTPNotFound
213 from pyramid.httpexceptions import HTTPNotFound
214 else:
214 else:
215 from webob.exc import HTTPNotFound
215 from webob.exc import HTTPNotFound
216
216
217 try:
217 try:
218 id_ = int(id_)
218 id_ = int(id_)
219 except (TypeError, ValueError):
219 except (TypeError, ValueError):
220 raise HTTPNotFound
220 raise HTTPNotFound
221
221
222 res = cls.query().get(id_)
222 res = cls.query().get(id_)
223 if not res:
223 if not res:
224 raise HTTPNotFound
224 raise HTTPNotFound
225 return res
225 return res
226
226
227 @classmethod
227 @classmethod
228 def getAll(cls):
228 def getAll(cls):
229 # deprecated and left for backward compatibility
229 # deprecated and left for backward compatibility
230 return cls.get_all()
230 return cls.get_all()
231
231
232 @classmethod
232 @classmethod
233 def get_all(cls):
233 def get_all(cls):
234 return cls.query().all()
234 return cls.query().all()
235
235
236 @classmethod
236 @classmethod
237 def delete(cls, id_):
237 def delete(cls, id_):
238 obj = cls.query().get(id_)
238 obj = cls.query().get(id_)
239 Session().delete(obj)
239 Session().delete(obj)
240
240
241 @classmethod
241 @classmethod
242 def identity_cache(cls, session, attr_name, value):
242 def identity_cache(cls, session, attr_name, value):
243 exist_in_session = []
243 exist_in_session = []
244 for (item_cls, pkey), instance in session.identity_map.items():
244 for (item_cls, pkey), instance in session.identity_map.items():
245 if cls == item_cls and getattr(instance, attr_name) == value:
245 if cls == item_cls and getattr(instance, attr_name) == value:
246 exist_in_session.append(instance)
246 exist_in_session.append(instance)
247 if exist_in_session:
247 if exist_in_session:
248 if len(exist_in_session) == 1:
248 if len(exist_in_session) == 1:
249 return exist_in_session[0]
249 return exist_in_session[0]
250 log.exception(
250 log.exception(
251 'multiple objects with attr %s and '
251 'multiple objects with attr %s and '
252 'value %s found with same name: %r',
252 'value %s found with same name: %r',
253 attr_name, value, exist_in_session)
253 attr_name, value, exist_in_session)
254
254
255 def __repr__(self):
255 def __repr__(self):
256 if hasattr(self, '__unicode__'):
256 if hasattr(self, '__unicode__'):
257 # python repr needs to return str
257 # python repr needs to return str
258 try:
258 try:
259 return safe_str(self.__unicode__())
259 return safe_str(self.__unicode__())
260 except UnicodeDecodeError:
260 except UnicodeDecodeError:
261 pass
261 pass
262 return '<DB:%s>' % (self.__class__.__name__)
262 return '<DB:%s>' % (self.__class__.__name__)
263
263
264
264
265 class RhodeCodeSetting(Base, BaseModel):
265 class RhodeCodeSetting(Base, BaseModel):
266 __tablename__ = 'rhodecode_settings'
266 __tablename__ = 'rhodecode_settings'
267 __table_args__ = (
267 __table_args__ = (
268 UniqueConstraint('app_settings_name'),
268 UniqueConstraint('app_settings_name'),
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
270 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
271 )
271 )
272
272
273 SETTINGS_TYPES = {
273 SETTINGS_TYPES = {
274 'str': safe_str,
274 'str': safe_str,
275 'int': safe_int,
275 'int': safe_int,
276 'unicode': safe_unicode,
276 'unicode': safe_unicode,
277 'bool': str2bool,
277 'bool': str2bool,
278 'list': functools.partial(aslist, sep=',')
278 'list': functools.partial(aslist, sep=',')
279 }
279 }
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
280 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
281 GLOBAL_CONF_KEY = 'app_settings'
281 GLOBAL_CONF_KEY = 'app_settings'
282
282
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
283 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
284 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
285 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
286 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
287
287
288 def __init__(self, key='', val='', type='unicode'):
288 def __init__(self, key='', val='', type='unicode'):
289 self.app_settings_name = key
289 self.app_settings_name = key
290 self.app_settings_type = type
290 self.app_settings_type = type
291 self.app_settings_value = val
291 self.app_settings_value = val
292
292
293 @validates('_app_settings_value')
293 @validates('_app_settings_value')
294 def validate_settings_value(self, key, val):
294 def validate_settings_value(self, key, val):
295 assert type(val) == unicode
295 assert type(val) == unicode
296 return val
296 return val
297
297
298 @hybrid_property
298 @hybrid_property
299 def app_settings_value(self):
299 def app_settings_value(self):
300 v = self._app_settings_value
300 v = self._app_settings_value
301 _type = self.app_settings_type
301 _type = self.app_settings_type
302 if _type:
302 if _type:
303 _type = self.app_settings_type.split('.')[0]
303 _type = self.app_settings_type.split('.')[0]
304 # decode the encrypted value
304 # decode the encrypted value
305 if 'encrypted' in self.app_settings_type:
305 if 'encrypted' in self.app_settings_type:
306 cipher = EncryptedTextValue()
306 cipher = EncryptedTextValue()
307 v = safe_unicode(cipher.process_result_value(v, None))
307 v = safe_unicode(cipher.process_result_value(v, None))
308
308
309 converter = self.SETTINGS_TYPES.get(_type) or \
309 converter = self.SETTINGS_TYPES.get(_type) or \
310 self.SETTINGS_TYPES['unicode']
310 self.SETTINGS_TYPES['unicode']
311 return converter(v)
311 return converter(v)
312
312
313 @app_settings_value.setter
313 @app_settings_value.setter
314 def app_settings_value(self, val):
314 def app_settings_value(self, val):
315 """
315 """
316 Setter that will always make sure we use unicode in app_settings_value
316 Setter that will always make sure we use unicode in app_settings_value
317
317
318 :param val:
318 :param val:
319 """
319 """
320 val = safe_unicode(val)
320 val = safe_unicode(val)
321 # encode the encrypted value
321 # encode the encrypted value
322 if 'encrypted' in self.app_settings_type:
322 if 'encrypted' in self.app_settings_type:
323 cipher = EncryptedTextValue()
323 cipher = EncryptedTextValue()
324 val = safe_unicode(cipher.process_bind_param(val, None))
324 val = safe_unicode(cipher.process_bind_param(val, None))
325 self._app_settings_value = val
325 self._app_settings_value = val
326
326
327 @hybrid_property
327 @hybrid_property
328 def app_settings_type(self):
328 def app_settings_type(self):
329 return self._app_settings_type
329 return self._app_settings_type
330
330
331 @app_settings_type.setter
331 @app_settings_type.setter
332 def app_settings_type(self, val):
332 def app_settings_type(self, val):
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
333 if val.split('.')[0] not in self.SETTINGS_TYPES:
334 raise Exception('type must be one of %s got %s'
334 raise Exception('type must be one of %s got %s'
335 % (self.SETTINGS_TYPES.keys(), val))
335 % (self.SETTINGS_TYPES.keys(), val))
336 self._app_settings_type = val
336 self._app_settings_type = val
337
337
338 def __unicode__(self):
338 def __unicode__(self):
339 return u"<%s('%s:%s[%s]')>" % (
339 return u"<%s('%s:%s[%s]')>" % (
340 self.__class__.__name__,
340 self.__class__.__name__,
341 self.app_settings_name, self.app_settings_value,
341 self.app_settings_name, self.app_settings_value,
342 self.app_settings_type
342 self.app_settings_type
343 )
343 )
344
344
345
345
346 class RhodeCodeUi(Base, BaseModel):
346 class RhodeCodeUi(Base, BaseModel):
347 __tablename__ = 'rhodecode_ui'
347 __tablename__ = 'rhodecode_ui'
348 __table_args__ = (
348 __table_args__ = (
349 UniqueConstraint('ui_key'),
349 UniqueConstraint('ui_key'),
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
350 {'extend_existing': True, 'mysql_engine': 'InnoDB',
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
351 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
352 )
352 )
353
353
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
354 HOOK_REPO_SIZE = 'changegroup.repo_size'
355 # HG
355 # HG
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
356 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
357 HOOK_PULL = 'outgoing.pull_logger'
357 HOOK_PULL = 'outgoing.pull_logger'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
358 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
359 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
360 HOOK_PUSH = 'changegroup.push_logger'
360 HOOK_PUSH = 'changegroup.push_logger'
361
361
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
362 # TODO: johbo: Unify way how hooks are configured for git and hg,
363 # git part is currently hardcoded.
363 # git part is currently hardcoded.
364
364
365 # SVN PATTERNS
365 # SVN PATTERNS
366 SVN_BRANCH_ID = 'vcs_svn_branch'
366 SVN_BRANCH_ID = 'vcs_svn_branch'
367 SVN_TAG_ID = 'vcs_svn_tag'
367 SVN_TAG_ID = 'vcs_svn_tag'
368
368
369 ui_id = Column(
369 ui_id = Column(
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
370 "ui_id", Integer(), nullable=False, unique=True, default=None,
371 primary_key=True)
371 primary_key=True)
372 ui_section = Column(
372 ui_section = Column(
373 "ui_section", String(255), nullable=True, unique=None, default=None)
373 "ui_section", String(255), nullable=True, unique=None, default=None)
374 ui_key = Column(
374 ui_key = Column(
375 "ui_key", String(255), nullable=True, unique=None, default=None)
375 "ui_key", String(255), nullable=True, unique=None, default=None)
376 ui_value = Column(
376 ui_value = Column(
377 "ui_value", String(255), nullable=True, unique=None, default=None)
377 "ui_value", String(255), nullable=True, unique=None, default=None)
378 ui_active = Column(
378 ui_active = Column(
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
379 "ui_active", Boolean(), nullable=True, unique=None, default=True)
380
380
381 def __repr__(self):
381 def __repr__(self):
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
382 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
383 self.ui_key, self.ui_value)
383 self.ui_key, self.ui_value)
384
384
385
385
386 class RepoRhodeCodeSetting(Base, BaseModel):
386 class RepoRhodeCodeSetting(Base, BaseModel):
387 __tablename__ = 'repo_rhodecode_settings'
387 __tablename__ = 'repo_rhodecode_settings'
388 __table_args__ = (
388 __table_args__ = (
389 UniqueConstraint(
389 UniqueConstraint(
390 'app_settings_name', 'repository_id',
390 'app_settings_name', 'repository_id',
391 name='uq_repo_rhodecode_setting_name_repo_id'),
391 name='uq_repo_rhodecode_setting_name_repo_id'),
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
394 )
394 )
395
395
396 repository_id = Column(
396 repository_id = Column(
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
397 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
398 nullable=False)
398 nullable=False)
399 app_settings_id = Column(
399 app_settings_id = Column(
400 "app_settings_id", Integer(), nullable=False, unique=True,
400 "app_settings_id", Integer(), nullable=False, unique=True,
401 default=None, primary_key=True)
401 default=None, primary_key=True)
402 app_settings_name = Column(
402 app_settings_name = Column(
403 "app_settings_name", String(255), nullable=True, unique=None,
403 "app_settings_name", String(255), nullable=True, unique=None,
404 default=None)
404 default=None)
405 _app_settings_value = Column(
405 _app_settings_value = Column(
406 "app_settings_value", String(4096), nullable=True, unique=None,
406 "app_settings_value", String(4096), nullable=True, unique=None,
407 default=None)
407 default=None)
408 _app_settings_type = Column(
408 _app_settings_type = Column(
409 "app_settings_type", String(255), nullable=True, unique=None,
409 "app_settings_type", String(255), nullable=True, unique=None,
410 default=None)
410 default=None)
411
411
412 repository = relationship('Repository')
412 repository = relationship('Repository')
413
413
414 def __init__(self, repository_id, key='', val='', type='unicode'):
414 def __init__(self, repository_id, key='', val='', type='unicode'):
415 self.repository_id = repository_id
415 self.repository_id = repository_id
416 self.app_settings_name = key
416 self.app_settings_name = key
417 self.app_settings_type = type
417 self.app_settings_type = type
418 self.app_settings_value = val
418 self.app_settings_value = val
419
419
420 @validates('_app_settings_value')
420 @validates('_app_settings_value')
421 def validate_settings_value(self, key, val):
421 def validate_settings_value(self, key, val):
422 assert type(val) == unicode
422 assert type(val) == unicode
423 return val
423 return val
424
424
425 @hybrid_property
425 @hybrid_property
426 def app_settings_value(self):
426 def app_settings_value(self):
427 v = self._app_settings_value
427 v = self._app_settings_value
428 type_ = self.app_settings_type
428 type_ = self.app_settings_type
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
429 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
430 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
431 return converter(v)
431 return converter(v)
432
432
433 @app_settings_value.setter
433 @app_settings_value.setter
434 def app_settings_value(self, val):
434 def app_settings_value(self, val):
435 """
435 """
436 Setter that will always make sure we use unicode in app_settings_value
436 Setter that will always make sure we use unicode in app_settings_value
437
437
438 :param val:
438 :param val:
439 """
439 """
440 self._app_settings_value = safe_unicode(val)
440 self._app_settings_value = safe_unicode(val)
441
441
442 @hybrid_property
442 @hybrid_property
443 def app_settings_type(self):
443 def app_settings_type(self):
444 return self._app_settings_type
444 return self._app_settings_type
445
445
446 @app_settings_type.setter
446 @app_settings_type.setter
447 def app_settings_type(self, val):
447 def app_settings_type(self, val):
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
448 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
449 if val not in SETTINGS_TYPES:
449 if val not in SETTINGS_TYPES:
450 raise Exception('type must be one of %s got %s'
450 raise Exception('type must be one of %s got %s'
451 % (SETTINGS_TYPES.keys(), val))
451 % (SETTINGS_TYPES.keys(), val))
452 self._app_settings_type = val
452 self._app_settings_type = val
453
453
454 def __unicode__(self):
454 def __unicode__(self):
455 return u"<%s('%s:%s:%s[%s]')>" % (
455 return u"<%s('%s:%s:%s[%s]')>" % (
456 self.__class__.__name__, self.repository.repo_name,
456 self.__class__.__name__, self.repository.repo_name,
457 self.app_settings_name, self.app_settings_value,
457 self.app_settings_name, self.app_settings_value,
458 self.app_settings_type
458 self.app_settings_type
459 )
459 )
460
460
461
461
462 class RepoRhodeCodeUi(Base, BaseModel):
462 class RepoRhodeCodeUi(Base, BaseModel):
463 __tablename__ = 'repo_rhodecode_ui'
463 __tablename__ = 'repo_rhodecode_ui'
464 __table_args__ = (
464 __table_args__ = (
465 UniqueConstraint(
465 UniqueConstraint(
466 'repository_id', 'ui_section', 'ui_key',
466 'repository_id', 'ui_section', 'ui_key',
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
467 name='uq_repo_rhodecode_ui_repository_id_section_key'),
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
469 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
470 )
470 )
471
471
472 repository_id = Column(
472 repository_id = Column(
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
473 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
474 nullable=False)
474 nullable=False)
475 ui_id = Column(
475 ui_id = Column(
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
476 "ui_id", Integer(), nullable=False, unique=True, default=None,
477 primary_key=True)
477 primary_key=True)
478 ui_section = Column(
478 ui_section = Column(
479 "ui_section", String(255), nullable=True, unique=None, default=None)
479 "ui_section", String(255), nullable=True, unique=None, default=None)
480 ui_key = Column(
480 ui_key = Column(
481 "ui_key", String(255), nullable=True, unique=None, default=None)
481 "ui_key", String(255), nullable=True, unique=None, default=None)
482 ui_value = Column(
482 ui_value = Column(
483 "ui_value", String(255), nullable=True, unique=None, default=None)
483 "ui_value", String(255), nullable=True, unique=None, default=None)
484 ui_active = Column(
484 ui_active = Column(
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
485 "ui_active", Boolean(), nullable=True, unique=None, default=True)
486
486
487 repository = relationship('Repository')
487 repository = relationship('Repository')
488
488
489 def __repr__(self):
489 def __repr__(self):
490 return '<%s[%s:%s]%s=>%s]>' % (
490 return '<%s[%s:%s]%s=>%s]>' % (
491 self.__class__.__name__, self.repository.repo_name,
491 self.__class__.__name__, self.repository.repo_name,
492 self.ui_section, self.ui_key, self.ui_value)
492 self.ui_section, self.ui_key, self.ui_value)
493
493
494
494
495 class User(Base, BaseModel):
495 class User(Base, BaseModel):
496 __tablename__ = 'users'
496 __tablename__ = 'users'
497 __table_args__ = (
497 __table_args__ = (
498 UniqueConstraint('username'), UniqueConstraint('email'),
498 UniqueConstraint('username'), UniqueConstraint('email'),
499 Index('u_username_idx', 'username'),
499 Index('u_username_idx', 'username'),
500 Index('u_email_idx', 'email'),
500 Index('u_email_idx', 'email'),
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
502 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
503 )
503 )
504 DEFAULT_USER = 'default'
504 DEFAULT_USER = 'default'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
505 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
506 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
507
507
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
509 username = Column("username", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
510 password = Column("password", String(255), nullable=True, unique=None, default=None)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
511 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
512 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
513 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
514 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
515 _email = Column("email", String(255), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
516 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
517 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
518
518
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
519 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
520 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
521 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
522 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
523 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
524 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
525
525
526 user_log = relationship('UserLog')
526 user_log = relationship('UserLog')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
527 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
528
528
529 repositories = relationship('Repository')
529 repositories = relationship('Repository')
530 repository_groups = relationship('RepoGroup')
530 repository_groups = relationship('RepoGroup')
531 user_groups = relationship('UserGroup')
531 user_groups = relationship('UserGroup')
532
532
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
533 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
534 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
535
535
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
536 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
537 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
538 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
539
539
540 group_member = relationship('UserGroupMember', cascade='all')
540 group_member = relationship('UserGroupMember', cascade='all')
541
541
542 notifications = relationship('UserNotification', cascade='all')
542 notifications = relationship('UserNotification', cascade='all')
543 # notifications assigned to this user
543 # notifications assigned to this user
544 user_created_notifications = relationship('Notification', cascade='all')
544 user_created_notifications = relationship('Notification', cascade='all')
545 # comments created by this user
545 # comments created by this user
546 user_comments = relationship('ChangesetComment', cascade='all')
546 user_comments = relationship('ChangesetComment', cascade='all')
547 # user profile extra info
547 # user profile extra info
548 user_emails = relationship('UserEmailMap', cascade='all')
548 user_emails = relationship('UserEmailMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
549 user_ip_map = relationship('UserIpMap', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
550 user_auth_tokens = relationship('UserApiKeys', cascade='all')
551 # gists
551 # gists
552 user_gists = relationship('Gist', cascade='all')
552 user_gists = relationship('Gist', cascade='all')
553 # user pull requests
553 # user pull requests
554 user_pull_requests = relationship('PullRequest', cascade='all')
554 user_pull_requests = relationship('PullRequest', cascade='all')
555 # external identities
555 # external identities
556 extenal_identities = relationship(
556 extenal_identities = relationship(
557 'ExternalIdentity',
557 'ExternalIdentity',
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
558 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
559 cascade='all')
559 cascade='all')
560
560
561 def __unicode__(self):
561 def __unicode__(self):
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
562 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
563 self.user_id, self.username)
563 self.user_id, self.username)
564
564
565 @hybrid_property
565 @hybrid_property
566 def email(self):
566 def email(self):
567 return self._email
567 return self._email
568
568
569 @email.setter
569 @email.setter
570 def email(self, val):
570 def email(self, val):
571 self._email = val.lower() if val else None
571 self._email = val.lower() if val else None
572
572
573 @hybrid_property
573 @hybrid_property
574 def api_key(self):
574 def api_key(self):
575 """
575 """
576 Fetch if exist an auth-token with role ALL connected to this user
576 Fetch if exist an auth-token with role ALL connected to this user
577 """
577 """
578 user_auth_token = UserApiKeys.query()\
578 user_auth_token = UserApiKeys.query()\
579 .filter(UserApiKeys.user_id == self.user_id)\
579 .filter(UserApiKeys.user_id == self.user_id)\
580 .filter(or_(UserApiKeys.expires == -1,
580 .filter(or_(UserApiKeys.expires == -1,
581 UserApiKeys.expires >= time.time()))\
581 UserApiKeys.expires >= time.time()))\
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
582 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
583 if user_auth_token:
583 if user_auth_token:
584 user_auth_token = user_auth_token.api_key
584 user_auth_token = user_auth_token.api_key
585
585
586 return user_auth_token
586 return user_auth_token
587
587
588 @api_key.setter
588 @api_key.setter
589 def api_key(self, val):
589 def api_key(self, val):
590 # don't allow to set API key this is deprecated for now
590 # don't allow to set API key this is deprecated for now
591 self._api_key = None
591 self._api_key = None
592
592
593 @property
593 @property
594 def firstname(self):
594 def firstname(self):
595 # alias for future
595 # alias for future
596 return self.name
596 return self.name
597
597
598 @property
598 @property
599 def emails(self):
599 def emails(self):
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
600 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
601 return [self.email] + [x.email for x in other]
601 return [self.email] + [x.email for x in other]
602
602
603 @property
603 @property
604 def auth_tokens(self):
604 def auth_tokens(self):
605 return [x.api_key for x in self.extra_auth_tokens]
605 return [x.api_key for x in self.extra_auth_tokens]
606
606
607 @property
607 @property
608 def extra_auth_tokens(self):
608 def extra_auth_tokens(self):
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
609 return UserApiKeys.query().filter(UserApiKeys.user == self).all()
610
610
611 @property
611 @property
612 def feed_token(self):
612 def feed_token(self):
613 return self.get_feed_token()
613 return self.get_feed_token()
614
614
615 def get_feed_token(self):
615 def get_feed_token(self):
616 feed_tokens = UserApiKeys.query()\
616 feed_tokens = UserApiKeys.query()\
617 .filter(UserApiKeys.user == self)\
617 .filter(UserApiKeys.user == self)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
618 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
619 .all()
619 .all()
620 if feed_tokens:
620 if feed_tokens:
621 return feed_tokens[0].api_key
621 return feed_tokens[0].api_key
622 return 'NO_FEED_TOKEN_AVAILABLE'
622 return 'NO_FEED_TOKEN_AVAILABLE'
623
623
624 @classmethod
624 @classmethod
625 def extra_valid_auth_tokens(cls, user, role=None):
625 def extra_valid_auth_tokens(cls, user, role=None):
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
626 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
627 .filter(or_(UserApiKeys.expires == -1,
627 .filter(or_(UserApiKeys.expires == -1,
628 UserApiKeys.expires >= time.time()))
628 UserApiKeys.expires >= time.time()))
629 if role:
629 if role:
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
630 tokens = tokens.filter(or_(UserApiKeys.role == role,
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
631 UserApiKeys.role == UserApiKeys.ROLE_ALL))
632 return tokens.all()
632 return tokens.all()
633
633
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
634 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
635 from rhodecode.lib import auth
635 from rhodecode.lib import auth
636
636
637 log.debug('Trying to authenticate user: %s via auth-token, '
637 log.debug('Trying to authenticate user: %s via auth-token, '
638 'and roles: %s', self, roles)
638 'and roles: %s', self, roles)
639
639
640 if not auth_token:
640 if not auth_token:
641 return False
641 return False
642
642
643 crypto_backend = auth.crypto_backend()
643 crypto_backend = auth.crypto_backend()
644
644
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
645 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
646 tokens_q = UserApiKeys.query()\
646 tokens_q = UserApiKeys.query()\
647 .filter(UserApiKeys.user_id == self.user_id)\
647 .filter(UserApiKeys.user_id == self.user_id)\
648 .filter(or_(UserApiKeys.expires == -1,
648 .filter(or_(UserApiKeys.expires == -1,
649 UserApiKeys.expires >= time.time()))
649 UserApiKeys.expires >= time.time()))
650
650
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
651 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
652
652
653 plain_tokens = []
653 plain_tokens = []
654 hash_tokens = []
654 hash_tokens = []
655
655
656 for token in tokens_q.all():
656 for token in tokens_q.all():
657 # verify scope first
657 # verify scope first
658 if token.repo_id:
658 if token.repo_id:
659 # token has a scope, we need to verify it
659 # token has a scope, we need to verify it
660 if scope_repo_id != token.repo_id:
660 if scope_repo_id != token.repo_id:
661 log.debug(
661 log.debug(
662 'Scope mismatch: token has a set repo scope: %s, '
662 'Scope mismatch: token has a set repo scope: %s, '
663 'and calling scope is:%s, skipping further checks',
663 'and calling scope is:%s, skipping further checks',
664 token.repo, scope_repo_id)
664 token.repo, scope_repo_id)
665 # token has a scope, and it doesn't match, skip token
665 # token has a scope, and it doesn't match, skip token
666 continue
666 continue
667
667
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
668 if token.api_key.startswith(crypto_backend.ENC_PREF):
669 hash_tokens.append(token.api_key)
669 hash_tokens.append(token.api_key)
670 else:
670 else:
671 plain_tokens.append(token.api_key)
671 plain_tokens.append(token.api_key)
672
672
673 is_plain_match = auth_token in plain_tokens
673 is_plain_match = auth_token in plain_tokens
674 if is_plain_match:
674 if is_plain_match:
675 return True
675 return True
676
676
677 for hashed in hash_tokens:
677 for hashed in hash_tokens:
678 # TODO(marcink): this is expensive to calculate, but most secure
678 # TODO(marcink): this is expensive to calculate, but most secure
679 match = crypto_backend.hash_check(auth_token, hashed)
679 match = crypto_backend.hash_check(auth_token, hashed)
680 if match:
680 if match:
681 return True
681 return True
682
682
683 return False
683 return False
684
684
685 @property
685 @property
686 def ip_addresses(self):
686 def ip_addresses(self):
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
687 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
688 return [x.ip_addr for x in ret]
688 return [x.ip_addr for x in ret]
689
689
690 @property
690 @property
691 def username_and_name(self):
691 def username_and_name(self):
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
692 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
693
693
694 @property
694 @property
695 def username_or_name_or_email(self):
695 def username_or_name_or_email(self):
696 full_name = self.full_name if self.full_name is not ' ' else None
696 full_name = self.full_name if self.full_name is not ' ' else None
697 return self.username or full_name or self.email
697 return self.username or full_name or self.email
698
698
699 @property
699 @property
700 def full_name(self):
700 def full_name(self):
701 return '%s %s' % (self.firstname, self.lastname)
701 return '%s %s' % (self.firstname, self.lastname)
702
702
703 @property
703 @property
704 def full_name_or_username(self):
704 def full_name_or_username(self):
705 return ('%s %s' % (self.firstname, self.lastname)
705 return ('%s %s' % (self.firstname, self.lastname)
706 if (self.firstname and self.lastname) else self.username)
706 if (self.firstname and self.lastname) else self.username)
707
707
708 @property
708 @property
709 def full_contact(self):
709 def full_contact(self):
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
710 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
711
711
712 @property
712 @property
713 def short_contact(self):
713 def short_contact(self):
714 return '%s %s' % (self.firstname, self.lastname)
714 return '%s %s' % (self.firstname, self.lastname)
715
715
716 @property
716 @property
717 def is_admin(self):
717 def is_admin(self):
718 return self.admin
718 return self.admin
719
719
720 @property
720 @property
721 def AuthUser(self):
721 def AuthUser(self):
722 """
722 """
723 Returns instance of AuthUser for this user
723 Returns instance of AuthUser for this user
724 """
724 """
725 from rhodecode.lib.auth import AuthUser
725 from rhodecode.lib.auth import AuthUser
726 return AuthUser(user_id=self.user_id, username=self.username)
726 return AuthUser(user_id=self.user_id, username=self.username)
727
727
728 @hybrid_property
728 @hybrid_property
729 def user_data(self):
729 def user_data(self):
730 if not self._user_data:
730 if not self._user_data:
731 return {}
731 return {}
732
732
733 try:
733 try:
734 return json.loads(self._user_data)
734 return json.loads(self._user_data)
735 except TypeError:
735 except TypeError:
736 return {}
736 return {}
737
737
738 @user_data.setter
738 @user_data.setter
739 def user_data(self, val):
739 def user_data(self, val):
740 if not isinstance(val, dict):
740 if not isinstance(val, dict):
741 raise Exception('user_data must be dict, got %s' % type(val))
741 raise Exception('user_data must be dict, got %s' % type(val))
742 try:
742 try:
743 self._user_data = json.dumps(val)
743 self._user_data = json.dumps(val)
744 except Exception:
744 except Exception:
745 log.error(traceback.format_exc())
745 log.error(traceback.format_exc())
746
746
747 @classmethod
747 @classmethod
748 def get_by_username(cls, username, case_insensitive=False,
748 def get_by_username(cls, username, case_insensitive=False,
749 cache=False, identity_cache=False):
749 cache=False, identity_cache=False):
750 session = Session()
750 session = Session()
751
751
752 if case_insensitive:
752 if case_insensitive:
753 q = cls.query().filter(
753 q = cls.query().filter(
754 func.lower(cls.username) == func.lower(username))
754 func.lower(cls.username) == func.lower(username))
755 else:
755 else:
756 q = cls.query().filter(cls.username == username)
756 q = cls.query().filter(cls.username == username)
757
757
758 if cache:
758 if cache:
759 if identity_cache:
759 if identity_cache:
760 val = cls.identity_cache(session, 'username', username)
760 val = cls.identity_cache(session, 'username', username)
761 if val:
761 if val:
762 return val
762 return val
763 else:
763 else:
764 q = q.options(
764 q = q.options(
765 FromCache("sql_cache_short",
765 FromCache("sql_cache_short",
766 "get_user_by_name_%s" % _hash_key(username)))
766 "get_user_by_name_%s" % _hash_key(username)))
767
767
768 return q.scalar()
768 return q.scalar()
769
769
770 @classmethod
770 @classmethod
771 def get_by_auth_token(cls, auth_token, cache=False):
771 def get_by_auth_token(cls, auth_token, cache=False):
772 q = UserApiKeys.query()\
772 q = UserApiKeys.query()\
773 .filter(UserApiKeys.api_key == auth_token)\
773 .filter(UserApiKeys.api_key == auth_token)\
774 .filter(or_(UserApiKeys.expires == -1,
774 .filter(or_(UserApiKeys.expires == -1,
775 UserApiKeys.expires >= time.time()))
775 UserApiKeys.expires >= time.time()))
776 if cache:
776 if cache:
777 q = q.options(FromCache("sql_cache_short",
777 q = q.options(FromCache("sql_cache_short",
778 "get_auth_token_%s" % auth_token))
778 "get_auth_token_%s" % auth_token))
779
779
780 match = q.first()
780 match = q.first()
781 if match:
781 if match:
782 return match.user
782 return match.user
783
783
784 @classmethod
784 @classmethod
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
785 def get_by_email(cls, email, case_insensitive=False, cache=False):
786
786
787 if case_insensitive:
787 if case_insensitive:
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
788 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
789
789
790 else:
790 else:
791 q = cls.query().filter(cls.email == email)
791 q = cls.query().filter(cls.email == email)
792
792
793 if cache:
793 if cache:
794 q = q.options(FromCache("sql_cache_short",
794 q = q.options(FromCache("sql_cache_short",
795 "get_email_key_%s" % _hash_key(email)))
795 "get_email_key_%s" % _hash_key(email)))
796
796
797 ret = q.scalar()
797 ret = q.scalar()
798 if ret is None:
798 if ret is None:
799 q = UserEmailMap.query()
799 q = UserEmailMap.query()
800 # try fetching in alternate email map
800 # try fetching in alternate email map
801 if case_insensitive:
801 if case_insensitive:
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
802 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
803 else:
803 else:
804 q = q.filter(UserEmailMap.email == email)
804 q = q.filter(UserEmailMap.email == email)
805 q = q.options(joinedload(UserEmailMap.user))
805 q = q.options(joinedload(UserEmailMap.user))
806 if cache:
806 if cache:
807 q = q.options(FromCache("sql_cache_short",
807 q = q.options(FromCache("sql_cache_short",
808 "get_email_map_key_%s" % email))
808 "get_email_map_key_%s" % email))
809 ret = getattr(q.scalar(), 'user', None)
809 ret = getattr(q.scalar(), 'user', None)
810
810
811 return ret
811 return ret
812
812
813 @classmethod
813 @classmethod
814 def get_from_cs_author(cls, author):
814 def get_from_cs_author(cls, author):
815 """
815 """
816 Tries to get User objects out of commit author string
816 Tries to get User objects out of commit author string
817
817
818 :param author:
818 :param author:
819 """
819 """
820 from rhodecode.lib.helpers import email, author_name
820 from rhodecode.lib.helpers import email, author_name
821 # Valid email in the attribute passed, see if they're in the system
821 # Valid email in the attribute passed, see if they're in the system
822 _email = email(author)
822 _email = email(author)
823 if _email:
823 if _email:
824 user = cls.get_by_email(_email, case_insensitive=True)
824 user = cls.get_by_email(_email, case_insensitive=True)
825 if user:
825 if user:
826 return user
826 return user
827 # Maybe we can match by username?
827 # Maybe we can match by username?
828 _author = author_name(author)
828 _author = author_name(author)
829 user = cls.get_by_username(_author, case_insensitive=True)
829 user = cls.get_by_username(_author, case_insensitive=True)
830 if user:
830 if user:
831 return user
831 return user
832
832
833 def update_userdata(self, **kwargs):
833 def update_userdata(self, **kwargs):
834 usr = self
834 usr = self
835 old = usr.user_data
835 old = usr.user_data
836 old.update(**kwargs)
836 old.update(**kwargs)
837 usr.user_data = old
837 usr.user_data = old
838 Session().add(usr)
838 Session().add(usr)
839 log.debug('updated userdata with ', kwargs)
839 log.debug('updated userdata with ', kwargs)
840
840
841 def update_lastlogin(self):
841 def update_lastlogin(self):
842 """Update user lastlogin"""
842 """Update user lastlogin"""
843 self.last_login = datetime.datetime.now()
843 self.last_login = datetime.datetime.now()
844 Session().add(self)
844 Session().add(self)
845 log.debug('updated user %s lastlogin', self.username)
845 log.debug('updated user %s lastlogin', self.username)
846
846
847 def update_lastactivity(self):
847 def update_lastactivity(self):
848 """Update user lastactivity"""
848 """Update user lastactivity"""
849 usr = self
849 self.last_activity = datetime.datetime.now()
850 old = usr.user_data
850 Session().add(self)
851 old.update({'last_activity': time.time()})
851 log.debug('updated user %s lastactivity', self.username)
852 usr.user_data = old
853 Session().add(usr)
854 log.debug('updated user %s lastactivity', usr.username)
855
852
856 def update_password(self, new_password):
853 def update_password(self, new_password):
857 from rhodecode.lib.auth import get_crypt_password
854 from rhodecode.lib.auth import get_crypt_password
858
855
859 self.password = get_crypt_password(new_password)
856 self.password = get_crypt_password(new_password)
860 Session().add(self)
857 Session().add(self)
861
858
862 @classmethod
859 @classmethod
863 def get_first_super_admin(cls):
860 def get_first_super_admin(cls):
864 user = User.query().filter(User.admin == true()).first()
861 user = User.query().filter(User.admin == true()).first()
865 if user is None:
862 if user is None:
866 raise Exception('FATAL: Missing administrative account!')
863 raise Exception('FATAL: Missing administrative account!')
867 return user
864 return user
868
865
869 @classmethod
866 @classmethod
870 def get_all_super_admins(cls):
867 def get_all_super_admins(cls):
871 """
868 """
872 Returns all admin accounts sorted by username
869 Returns all admin accounts sorted by username
873 """
870 """
874 return User.query().filter(User.admin == true())\
871 return User.query().filter(User.admin == true())\
875 .order_by(User.username.asc()).all()
872 .order_by(User.username.asc()).all()
876
873
877 @classmethod
874 @classmethod
878 def get_default_user(cls, cache=False):
875 def get_default_user(cls, cache=False):
879 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
876 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
880 if user is None:
877 if user is None:
881 raise Exception('FATAL: Missing default account!')
878 raise Exception('FATAL: Missing default account!')
882 return user
879 return user
883
880
884 def _get_default_perms(self, user, suffix=''):
881 def _get_default_perms(self, user, suffix=''):
885 from rhodecode.model.permission import PermissionModel
882 from rhodecode.model.permission import PermissionModel
886 return PermissionModel().get_default_perms(user.user_perms, suffix)
883 return PermissionModel().get_default_perms(user.user_perms, suffix)
887
884
888 def get_default_perms(self, suffix=''):
885 def get_default_perms(self, suffix=''):
889 return self._get_default_perms(self, suffix)
886 return self._get_default_perms(self, suffix)
890
887
891 def get_api_data(self, include_secrets=False, details='full'):
888 def get_api_data(self, include_secrets=False, details='full'):
892 """
889 """
893 Common function for generating user related data for API
890 Common function for generating user related data for API
894
891
895 :param include_secrets: By default secrets in the API data will be replaced
892 :param include_secrets: By default secrets in the API data will be replaced
896 by a placeholder value to prevent exposing this data by accident. In case
893 by a placeholder value to prevent exposing this data by accident. In case
897 this data shall be exposed, set this flag to ``True``.
894 this data shall be exposed, set this flag to ``True``.
898
895
899 :param details: details can be 'basic|full' basic gives only a subset of
896 :param details: details can be 'basic|full' basic gives only a subset of
900 the available user information that includes user_id, name and emails.
897 the available user information that includes user_id, name and emails.
901 """
898 """
902 user = self
899 user = self
903 user_data = self.user_data
900 user_data = self.user_data
904 data = {
901 data = {
905 'user_id': user.user_id,
902 'user_id': user.user_id,
906 'username': user.username,
903 'username': user.username,
907 'firstname': user.name,
904 'firstname': user.name,
908 'lastname': user.lastname,
905 'lastname': user.lastname,
909 'email': user.email,
906 'email': user.email,
910 'emails': user.emails,
907 'emails': user.emails,
911 }
908 }
912 if details == 'basic':
909 if details == 'basic':
913 return data
910 return data
914
911
915 api_key_length = 40
912 api_key_length = 40
916 api_key_replacement = '*' * api_key_length
913 api_key_replacement = '*' * api_key_length
917
914
918 extras = {
915 extras = {
919 'api_keys': [api_key_replacement],
916 'api_keys': [api_key_replacement],
920 'auth_tokens': [api_key_replacement],
917 'auth_tokens': [api_key_replacement],
921 'active': user.active,
918 'active': user.active,
922 'admin': user.admin,
919 'admin': user.admin,
923 'extern_type': user.extern_type,
920 'extern_type': user.extern_type,
924 'extern_name': user.extern_name,
921 'extern_name': user.extern_name,
925 'last_login': user.last_login,
922 'last_login': user.last_login,
926 'ip_addresses': user.ip_addresses,
923 'ip_addresses': user.ip_addresses,
927 'language': user_data.get('language')
924 'language': user_data.get('language')
928 }
925 }
929 data.update(extras)
926 data.update(extras)
930
927
931 if include_secrets:
928 if include_secrets:
932 data['api_keys'] = user.auth_tokens
929 data['api_keys'] = user.auth_tokens
933 data['auth_tokens'] = user.extra_auth_tokens
930 data['auth_tokens'] = user.extra_auth_tokens
934 return data
931 return data
935
932
936 def __json__(self):
933 def __json__(self):
937 data = {
934 data = {
938 'full_name': self.full_name,
935 'full_name': self.full_name,
939 'full_name_or_username': self.full_name_or_username,
936 'full_name_or_username': self.full_name_or_username,
940 'short_contact': self.short_contact,
937 'short_contact': self.short_contact,
941 'full_contact': self.full_contact,
938 'full_contact': self.full_contact,
942 }
939 }
943 data.update(self.get_api_data())
940 data.update(self.get_api_data())
944 return data
941 return data
945
942
946
943
947 class UserApiKeys(Base, BaseModel):
944 class UserApiKeys(Base, BaseModel):
948 __tablename__ = 'user_api_keys'
945 __tablename__ = 'user_api_keys'
949 __table_args__ = (
946 __table_args__ = (
950 Index('uak_api_key_idx', 'api_key'),
947 Index('uak_api_key_idx', 'api_key'),
951 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
948 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
952 UniqueConstraint('api_key'),
949 UniqueConstraint('api_key'),
953 {'extend_existing': True, 'mysql_engine': 'InnoDB',
950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
954 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
951 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
955 )
952 )
956 __mapper_args__ = {}
953 __mapper_args__ = {}
957
954
958 # ApiKey role
955 # ApiKey role
959 ROLE_ALL = 'token_role_all'
956 ROLE_ALL = 'token_role_all'
960 ROLE_HTTP = 'token_role_http'
957 ROLE_HTTP = 'token_role_http'
961 ROLE_VCS = 'token_role_vcs'
958 ROLE_VCS = 'token_role_vcs'
962 ROLE_API = 'token_role_api'
959 ROLE_API = 'token_role_api'
963 ROLE_FEED = 'token_role_feed'
960 ROLE_FEED = 'token_role_feed'
964 ROLE_PASSWORD_RESET = 'token_password_reset'
961 ROLE_PASSWORD_RESET = 'token_password_reset'
965
962
966 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
963 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
967
964
968 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
965 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
969 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
966 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
970 api_key = Column("api_key", String(255), nullable=False, unique=True)
967 api_key = Column("api_key", String(255), nullable=False, unique=True)
971 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
968 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
972 expires = Column('expires', Float(53), nullable=False)
969 expires = Column('expires', Float(53), nullable=False)
973 role = Column('role', String(255), nullable=True)
970 role = Column('role', String(255), nullable=True)
974 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
971 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
975
972
976 # scope columns
973 # scope columns
977 repo_id = Column(
974 repo_id = Column(
978 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
975 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
979 nullable=True, unique=None, default=None)
976 nullable=True, unique=None, default=None)
980 repo = relationship('Repository', lazy='joined')
977 repo = relationship('Repository', lazy='joined')
981
978
982 repo_group_id = Column(
979 repo_group_id = Column(
983 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
980 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
984 nullable=True, unique=None, default=None)
981 nullable=True, unique=None, default=None)
985 repo_group = relationship('RepoGroup', lazy='joined')
982 repo_group = relationship('RepoGroup', lazy='joined')
986
983
987 user = relationship('User', lazy='joined')
984 user = relationship('User', lazy='joined')
988
985
989 def __unicode__(self):
986 def __unicode__(self):
990 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
987 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
991
988
992 def __json__(self):
989 def __json__(self):
993 data = {
990 data = {
994 'auth_token': self.api_key,
991 'auth_token': self.api_key,
995 'role': self.role,
992 'role': self.role,
996 'scope': self.scope_humanized,
993 'scope': self.scope_humanized,
997 'expired': self.expired
994 'expired': self.expired
998 }
995 }
999 return data
996 return data
1000
997
1001 @property
998 @property
1002 def expired(self):
999 def expired(self):
1003 if self.expires == -1:
1000 if self.expires == -1:
1004 return False
1001 return False
1005 return time.time() > self.expires
1002 return time.time() > self.expires
1006
1003
1007 @classmethod
1004 @classmethod
1008 def _get_role_name(cls, role):
1005 def _get_role_name(cls, role):
1009 return {
1006 return {
1010 cls.ROLE_ALL: _('all'),
1007 cls.ROLE_ALL: _('all'),
1011 cls.ROLE_HTTP: _('http/web interface'),
1008 cls.ROLE_HTTP: _('http/web interface'),
1012 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1009 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1013 cls.ROLE_API: _('api calls'),
1010 cls.ROLE_API: _('api calls'),
1014 cls.ROLE_FEED: _('feed access'),
1011 cls.ROLE_FEED: _('feed access'),
1015 }.get(role, role)
1012 }.get(role, role)
1016
1013
1017 @property
1014 @property
1018 def role_humanized(self):
1015 def role_humanized(self):
1019 return self._get_role_name(self.role)
1016 return self._get_role_name(self.role)
1020
1017
1021 def _get_scope(self):
1018 def _get_scope(self):
1022 if self.repo:
1019 if self.repo:
1023 return repr(self.repo)
1020 return repr(self.repo)
1024 if self.repo_group:
1021 if self.repo_group:
1025 return repr(self.repo_group) + ' (recursive)'
1022 return repr(self.repo_group) + ' (recursive)'
1026 return 'global'
1023 return 'global'
1027
1024
1028 @property
1025 @property
1029 def scope_humanized(self):
1026 def scope_humanized(self):
1030 return self._get_scope()
1027 return self._get_scope()
1031
1028
1032
1029
1033 class UserEmailMap(Base, BaseModel):
1030 class UserEmailMap(Base, BaseModel):
1034 __tablename__ = 'user_email_map'
1031 __tablename__ = 'user_email_map'
1035 __table_args__ = (
1032 __table_args__ = (
1036 Index('uem_email_idx', 'email'),
1033 Index('uem_email_idx', 'email'),
1037 UniqueConstraint('email'),
1034 UniqueConstraint('email'),
1038 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1039 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1036 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1040 )
1037 )
1041 __mapper_args__ = {}
1038 __mapper_args__ = {}
1042
1039
1043 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1040 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1045 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1042 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1046 user = relationship('User', lazy='joined')
1043 user = relationship('User', lazy='joined')
1047
1044
1048 @validates('_email')
1045 @validates('_email')
1049 def validate_email(self, key, email):
1046 def validate_email(self, key, email):
1050 # check if this email is not main one
1047 # check if this email is not main one
1051 main_email = Session().query(User).filter(User.email == email).scalar()
1048 main_email = Session().query(User).filter(User.email == email).scalar()
1052 if main_email is not None:
1049 if main_email is not None:
1053 raise AttributeError('email %s is present is user table' % email)
1050 raise AttributeError('email %s is present is user table' % email)
1054 return email
1051 return email
1055
1052
1056 @hybrid_property
1053 @hybrid_property
1057 def email(self):
1054 def email(self):
1058 return self._email
1055 return self._email
1059
1056
1060 @email.setter
1057 @email.setter
1061 def email(self, val):
1058 def email(self, val):
1062 self._email = val.lower() if val else None
1059 self._email = val.lower() if val else None
1063
1060
1064
1061
1065 class UserIpMap(Base, BaseModel):
1062 class UserIpMap(Base, BaseModel):
1066 __tablename__ = 'user_ip_map'
1063 __tablename__ = 'user_ip_map'
1067 __table_args__ = (
1064 __table_args__ = (
1068 UniqueConstraint('user_id', 'ip_addr'),
1065 UniqueConstraint('user_id', 'ip_addr'),
1069 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1070 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1067 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1071 )
1068 )
1072 __mapper_args__ = {}
1069 __mapper_args__ = {}
1073
1070
1074 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1071 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1072 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1076 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1073 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1077 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1074 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1078 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1075 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1079 user = relationship('User', lazy='joined')
1076 user = relationship('User', lazy='joined')
1080
1077
1081 @classmethod
1078 @classmethod
1082 def _get_ip_range(cls, ip_addr):
1079 def _get_ip_range(cls, ip_addr):
1083 net = ipaddress.ip_network(ip_addr, strict=False)
1080 net = ipaddress.ip_network(ip_addr, strict=False)
1084 return [str(net.network_address), str(net.broadcast_address)]
1081 return [str(net.network_address), str(net.broadcast_address)]
1085
1082
1086 def __json__(self):
1083 def __json__(self):
1087 return {
1084 return {
1088 'ip_addr': self.ip_addr,
1085 'ip_addr': self.ip_addr,
1089 'ip_range': self._get_ip_range(self.ip_addr),
1086 'ip_range': self._get_ip_range(self.ip_addr),
1090 }
1087 }
1091
1088
1092 def __unicode__(self):
1089 def __unicode__(self):
1093 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1090 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1094 self.user_id, self.ip_addr)
1091 self.user_id, self.ip_addr)
1095
1092
1096
1093
1097 class UserLog(Base, BaseModel):
1094 class UserLog(Base, BaseModel):
1098 __tablename__ = 'user_logs'
1095 __tablename__ = 'user_logs'
1099 __table_args__ = (
1096 __table_args__ = (
1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1098 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1102 )
1099 )
1103 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1100 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1104 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1105 username = Column("username", String(255), nullable=True, unique=None, default=None)
1102 username = Column("username", String(255), nullable=True, unique=None, default=None)
1106 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1103 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1107 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1104 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1108 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1105 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1109 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1106 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1110 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1107 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1111
1108
1112 def __unicode__(self):
1109 def __unicode__(self):
1113 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1110 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1114 self.repository_name,
1111 self.repository_name,
1115 self.action)
1112 self.action)
1116
1113
1117 @property
1114 @property
1118 def action_as_day(self):
1115 def action_as_day(self):
1119 return datetime.date(*self.action_date.timetuple()[:3])
1116 return datetime.date(*self.action_date.timetuple()[:3])
1120
1117
1121 user = relationship('User')
1118 user = relationship('User')
1122 repository = relationship('Repository', cascade='')
1119 repository = relationship('Repository', cascade='')
1123
1120
1124
1121
1125 class UserGroup(Base, BaseModel):
1122 class UserGroup(Base, BaseModel):
1126 __tablename__ = 'users_groups'
1123 __tablename__ = 'users_groups'
1127 __table_args__ = (
1124 __table_args__ = (
1128 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1125 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1129 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1126 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1130 )
1127 )
1131
1128
1132 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1129 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1133 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1130 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1134 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1131 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1135 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1132 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1136 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1133 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1137 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1134 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1138 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1135 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1139 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1136 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1140
1137
1141 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1138 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1142 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1139 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1143 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1140 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1144 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1141 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1145 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1142 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1146 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1143 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1147
1144
1148 user = relationship('User')
1145 user = relationship('User')
1149
1146
1150 @hybrid_property
1147 @hybrid_property
1151 def group_data(self):
1148 def group_data(self):
1152 if not self._group_data:
1149 if not self._group_data:
1153 return {}
1150 return {}
1154
1151
1155 try:
1152 try:
1156 return json.loads(self._group_data)
1153 return json.loads(self._group_data)
1157 except TypeError:
1154 except TypeError:
1158 return {}
1155 return {}
1159
1156
1160 @group_data.setter
1157 @group_data.setter
1161 def group_data(self, val):
1158 def group_data(self, val):
1162 try:
1159 try:
1163 self._group_data = json.dumps(val)
1160 self._group_data = json.dumps(val)
1164 except Exception:
1161 except Exception:
1165 log.error(traceback.format_exc())
1162 log.error(traceback.format_exc())
1166
1163
1167 def __unicode__(self):
1164 def __unicode__(self):
1168 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1165 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1169 self.users_group_id,
1166 self.users_group_id,
1170 self.users_group_name)
1167 self.users_group_name)
1171
1168
1172 @classmethod
1169 @classmethod
1173 def get_by_group_name(cls, group_name, cache=False,
1170 def get_by_group_name(cls, group_name, cache=False,
1174 case_insensitive=False):
1171 case_insensitive=False):
1175 if case_insensitive:
1172 if case_insensitive:
1176 q = cls.query().filter(func.lower(cls.users_group_name) ==
1173 q = cls.query().filter(func.lower(cls.users_group_name) ==
1177 func.lower(group_name))
1174 func.lower(group_name))
1178
1175
1179 else:
1176 else:
1180 q = cls.query().filter(cls.users_group_name == group_name)
1177 q = cls.query().filter(cls.users_group_name == group_name)
1181 if cache:
1178 if cache:
1182 q = q.options(FromCache(
1179 q = q.options(FromCache(
1183 "sql_cache_short",
1180 "sql_cache_short",
1184 "get_group_%s" % _hash_key(group_name)))
1181 "get_group_%s" % _hash_key(group_name)))
1185 return q.scalar()
1182 return q.scalar()
1186
1183
1187 @classmethod
1184 @classmethod
1188 def get(cls, user_group_id, cache=False):
1185 def get(cls, user_group_id, cache=False):
1189 user_group = cls.query()
1186 user_group = cls.query()
1190 if cache:
1187 if cache:
1191 user_group = user_group.options(FromCache("sql_cache_short",
1188 user_group = user_group.options(FromCache("sql_cache_short",
1192 "get_users_group_%s" % user_group_id))
1189 "get_users_group_%s" % user_group_id))
1193 return user_group.get(user_group_id)
1190 return user_group.get(user_group_id)
1194
1191
1195 def permissions(self, with_admins=True, with_owner=True):
1192 def permissions(self, with_admins=True, with_owner=True):
1196 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1193 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1197 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1194 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1198 joinedload(UserUserGroupToPerm.user),
1195 joinedload(UserUserGroupToPerm.user),
1199 joinedload(UserUserGroupToPerm.permission),)
1196 joinedload(UserUserGroupToPerm.permission),)
1200
1197
1201 # get owners and admins and permissions. We do a trick of re-writing
1198 # get owners and admins and permissions. We do a trick of re-writing
1202 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1199 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1203 # has a global reference and changing one object propagates to all
1200 # has a global reference and changing one object propagates to all
1204 # others. This means if admin is also an owner admin_row that change
1201 # others. This means if admin is also an owner admin_row that change
1205 # would propagate to both objects
1202 # would propagate to both objects
1206 perm_rows = []
1203 perm_rows = []
1207 for _usr in q.all():
1204 for _usr in q.all():
1208 usr = AttributeDict(_usr.user.get_dict())
1205 usr = AttributeDict(_usr.user.get_dict())
1209 usr.permission = _usr.permission.permission_name
1206 usr.permission = _usr.permission.permission_name
1210 perm_rows.append(usr)
1207 perm_rows.append(usr)
1211
1208
1212 # filter the perm rows by 'default' first and then sort them by
1209 # filter the perm rows by 'default' first and then sort them by
1213 # admin,write,read,none permissions sorted again alphabetically in
1210 # admin,write,read,none permissions sorted again alphabetically in
1214 # each group
1211 # each group
1215 perm_rows = sorted(perm_rows, key=display_sort)
1212 perm_rows = sorted(perm_rows, key=display_sort)
1216
1213
1217 _admin_perm = 'usergroup.admin'
1214 _admin_perm = 'usergroup.admin'
1218 owner_row = []
1215 owner_row = []
1219 if with_owner:
1216 if with_owner:
1220 usr = AttributeDict(self.user.get_dict())
1217 usr = AttributeDict(self.user.get_dict())
1221 usr.owner_row = True
1218 usr.owner_row = True
1222 usr.permission = _admin_perm
1219 usr.permission = _admin_perm
1223 owner_row.append(usr)
1220 owner_row.append(usr)
1224
1221
1225 super_admin_rows = []
1222 super_admin_rows = []
1226 if with_admins:
1223 if with_admins:
1227 for usr in User.get_all_super_admins():
1224 for usr in User.get_all_super_admins():
1228 # if this admin is also owner, don't double the record
1225 # if this admin is also owner, don't double the record
1229 if usr.user_id == owner_row[0].user_id:
1226 if usr.user_id == owner_row[0].user_id:
1230 owner_row[0].admin_row = True
1227 owner_row[0].admin_row = True
1231 else:
1228 else:
1232 usr = AttributeDict(usr.get_dict())
1229 usr = AttributeDict(usr.get_dict())
1233 usr.admin_row = True
1230 usr.admin_row = True
1234 usr.permission = _admin_perm
1231 usr.permission = _admin_perm
1235 super_admin_rows.append(usr)
1232 super_admin_rows.append(usr)
1236
1233
1237 return super_admin_rows + owner_row + perm_rows
1234 return super_admin_rows + owner_row + perm_rows
1238
1235
1239 def permission_user_groups(self):
1236 def permission_user_groups(self):
1240 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1237 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1241 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1238 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1242 joinedload(UserGroupUserGroupToPerm.target_user_group),
1239 joinedload(UserGroupUserGroupToPerm.target_user_group),
1243 joinedload(UserGroupUserGroupToPerm.permission),)
1240 joinedload(UserGroupUserGroupToPerm.permission),)
1244
1241
1245 perm_rows = []
1242 perm_rows = []
1246 for _user_group in q.all():
1243 for _user_group in q.all():
1247 usr = AttributeDict(_user_group.user_group.get_dict())
1244 usr = AttributeDict(_user_group.user_group.get_dict())
1248 usr.permission = _user_group.permission.permission_name
1245 usr.permission = _user_group.permission.permission_name
1249 perm_rows.append(usr)
1246 perm_rows.append(usr)
1250
1247
1251 return perm_rows
1248 return perm_rows
1252
1249
1253 def _get_default_perms(self, user_group, suffix=''):
1250 def _get_default_perms(self, user_group, suffix=''):
1254 from rhodecode.model.permission import PermissionModel
1251 from rhodecode.model.permission import PermissionModel
1255 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1252 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1256
1253
1257 def get_default_perms(self, suffix=''):
1254 def get_default_perms(self, suffix=''):
1258 return self._get_default_perms(self, suffix)
1255 return self._get_default_perms(self, suffix)
1259
1256
1260 def get_api_data(self, with_group_members=True, include_secrets=False):
1257 def get_api_data(self, with_group_members=True, include_secrets=False):
1261 """
1258 """
1262 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1259 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1263 basically forwarded.
1260 basically forwarded.
1264
1261
1265 """
1262 """
1266 user_group = self
1263 user_group = self
1267
1264
1268 data = {
1265 data = {
1269 'users_group_id': user_group.users_group_id,
1266 'users_group_id': user_group.users_group_id,
1270 'group_name': user_group.users_group_name,
1267 'group_name': user_group.users_group_name,
1271 'group_description': user_group.user_group_description,
1268 'group_description': user_group.user_group_description,
1272 'active': user_group.users_group_active,
1269 'active': user_group.users_group_active,
1273 'owner': user_group.user.username,
1270 'owner': user_group.user.username,
1274 }
1271 }
1275 if with_group_members:
1272 if with_group_members:
1276 users = []
1273 users = []
1277 for user in user_group.members:
1274 for user in user_group.members:
1278 user = user.user
1275 user = user.user
1279 users.append(user.get_api_data(include_secrets=include_secrets))
1276 users.append(user.get_api_data(include_secrets=include_secrets))
1280 data['users'] = users
1277 data['users'] = users
1281
1278
1282 return data
1279 return data
1283
1280
1284
1281
1285 class UserGroupMember(Base, BaseModel):
1282 class UserGroupMember(Base, BaseModel):
1286 __tablename__ = 'users_groups_members'
1283 __tablename__ = 'users_groups_members'
1287 __table_args__ = (
1284 __table_args__ = (
1288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1285 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1289 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1286 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1290 )
1287 )
1291
1288
1292 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1289 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1293 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1290 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1294 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1291 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1295
1292
1296 user = relationship('User', lazy='joined')
1293 user = relationship('User', lazy='joined')
1297 users_group = relationship('UserGroup')
1294 users_group = relationship('UserGroup')
1298
1295
1299 def __init__(self, gr_id='', u_id=''):
1296 def __init__(self, gr_id='', u_id=''):
1300 self.users_group_id = gr_id
1297 self.users_group_id = gr_id
1301 self.user_id = u_id
1298 self.user_id = u_id
1302
1299
1303
1300
1304 class RepositoryField(Base, BaseModel):
1301 class RepositoryField(Base, BaseModel):
1305 __tablename__ = 'repositories_fields'
1302 __tablename__ = 'repositories_fields'
1306 __table_args__ = (
1303 __table_args__ = (
1307 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1304 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1308 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1309 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1306 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1310 )
1307 )
1311 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1308 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1312
1309
1313 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1310 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1314 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1311 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1315 field_key = Column("field_key", String(250))
1312 field_key = Column("field_key", String(250))
1316 field_label = Column("field_label", String(1024), nullable=False)
1313 field_label = Column("field_label", String(1024), nullable=False)
1317 field_value = Column("field_value", String(10000), nullable=False)
1314 field_value = Column("field_value", String(10000), nullable=False)
1318 field_desc = Column("field_desc", String(1024), nullable=False)
1315 field_desc = Column("field_desc", String(1024), nullable=False)
1319 field_type = Column("field_type", String(255), nullable=False, unique=None)
1316 field_type = Column("field_type", String(255), nullable=False, unique=None)
1320 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1317 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1321
1318
1322 repository = relationship('Repository')
1319 repository = relationship('Repository')
1323
1320
1324 @property
1321 @property
1325 def field_key_prefixed(self):
1322 def field_key_prefixed(self):
1326 return 'ex_%s' % self.field_key
1323 return 'ex_%s' % self.field_key
1327
1324
1328 @classmethod
1325 @classmethod
1329 def un_prefix_key(cls, key):
1326 def un_prefix_key(cls, key):
1330 if key.startswith(cls.PREFIX):
1327 if key.startswith(cls.PREFIX):
1331 return key[len(cls.PREFIX):]
1328 return key[len(cls.PREFIX):]
1332 return key
1329 return key
1333
1330
1334 @classmethod
1331 @classmethod
1335 def get_by_key_name(cls, key, repo):
1332 def get_by_key_name(cls, key, repo):
1336 row = cls.query()\
1333 row = cls.query()\
1337 .filter(cls.repository == repo)\
1334 .filter(cls.repository == repo)\
1338 .filter(cls.field_key == key).scalar()
1335 .filter(cls.field_key == key).scalar()
1339 return row
1336 return row
1340
1337
1341
1338
1342 class Repository(Base, BaseModel):
1339 class Repository(Base, BaseModel):
1343 __tablename__ = 'repositories'
1340 __tablename__ = 'repositories'
1344 __table_args__ = (
1341 __table_args__ = (
1345 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1342 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1347 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1344 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1348 )
1345 )
1349 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1346 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1350 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1347 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1351
1348
1352 STATE_CREATED = 'repo_state_created'
1349 STATE_CREATED = 'repo_state_created'
1353 STATE_PENDING = 'repo_state_pending'
1350 STATE_PENDING = 'repo_state_pending'
1354 STATE_ERROR = 'repo_state_error'
1351 STATE_ERROR = 'repo_state_error'
1355
1352
1356 LOCK_AUTOMATIC = 'lock_auto'
1353 LOCK_AUTOMATIC = 'lock_auto'
1357 LOCK_API = 'lock_api'
1354 LOCK_API = 'lock_api'
1358 LOCK_WEB = 'lock_web'
1355 LOCK_WEB = 'lock_web'
1359 LOCK_PULL = 'lock_pull'
1356 LOCK_PULL = 'lock_pull'
1360
1357
1361 NAME_SEP = URL_SEP
1358 NAME_SEP = URL_SEP
1362
1359
1363 repo_id = Column(
1360 repo_id = Column(
1364 "repo_id", Integer(), nullable=False, unique=True, default=None,
1361 "repo_id", Integer(), nullable=False, unique=True, default=None,
1365 primary_key=True)
1362 primary_key=True)
1366 _repo_name = Column(
1363 _repo_name = Column(
1367 "repo_name", Text(), nullable=False, default=None)
1364 "repo_name", Text(), nullable=False, default=None)
1368 _repo_name_hash = Column(
1365 _repo_name_hash = Column(
1369 "repo_name_hash", String(255), nullable=False, unique=True)
1366 "repo_name_hash", String(255), nullable=False, unique=True)
1370 repo_state = Column("repo_state", String(255), nullable=True)
1367 repo_state = Column("repo_state", String(255), nullable=True)
1371
1368
1372 clone_uri = Column(
1369 clone_uri = Column(
1373 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1370 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1374 default=None)
1371 default=None)
1375 repo_type = Column(
1372 repo_type = Column(
1376 "repo_type", String(255), nullable=False, unique=False, default=None)
1373 "repo_type", String(255), nullable=False, unique=False, default=None)
1377 user_id = Column(
1374 user_id = Column(
1378 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1375 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1379 unique=False, default=None)
1376 unique=False, default=None)
1380 private = Column(
1377 private = Column(
1381 "private", Boolean(), nullable=True, unique=None, default=None)
1378 "private", Boolean(), nullable=True, unique=None, default=None)
1382 enable_statistics = Column(
1379 enable_statistics = Column(
1383 "statistics", Boolean(), nullable=True, unique=None, default=True)
1380 "statistics", Boolean(), nullable=True, unique=None, default=True)
1384 enable_downloads = Column(
1381 enable_downloads = Column(
1385 "downloads", Boolean(), nullable=True, unique=None, default=True)
1382 "downloads", Boolean(), nullable=True, unique=None, default=True)
1386 description = Column(
1383 description = Column(
1387 "description", String(10000), nullable=True, unique=None, default=None)
1384 "description", String(10000), nullable=True, unique=None, default=None)
1388 created_on = Column(
1385 created_on = Column(
1389 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1386 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1390 default=datetime.datetime.now)
1387 default=datetime.datetime.now)
1391 updated_on = Column(
1388 updated_on = Column(
1392 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1389 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1393 default=datetime.datetime.now)
1390 default=datetime.datetime.now)
1394 _landing_revision = Column(
1391 _landing_revision = Column(
1395 "landing_revision", String(255), nullable=False, unique=False,
1392 "landing_revision", String(255), nullable=False, unique=False,
1396 default=None)
1393 default=None)
1397 enable_locking = Column(
1394 enable_locking = Column(
1398 "enable_locking", Boolean(), nullable=False, unique=None,
1395 "enable_locking", Boolean(), nullable=False, unique=None,
1399 default=False)
1396 default=False)
1400 _locked = Column(
1397 _locked = Column(
1401 "locked", String(255), nullable=True, unique=False, default=None)
1398 "locked", String(255), nullable=True, unique=False, default=None)
1402 _changeset_cache = Column(
1399 _changeset_cache = Column(
1403 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1400 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1404
1401
1405 fork_id = Column(
1402 fork_id = Column(
1406 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1403 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1407 nullable=True, unique=False, default=None)
1404 nullable=True, unique=False, default=None)
1408 group_id = Column(
1405 group_id = Column(
1409 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1406 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1410 unique=False, default=None)
1407 unique=False, default=None)
1411
1408
1412 user = relationship('User', lazy='joined')
1409 user = relationship('User', lazy='joined')
1413 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1410 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1414 group = relationship('RepoGroup', lazy='joined')
1411 group = relationship('RepoGroup', lazy='joined')
1415 repo_to_perm = relationship(
1412 repo_to_perm = relationship(
1416 'UserRepoToPerm', cascade='all',
1413 'UserRepoToPerm', cascade='all',
1417 order_by='UserRepoToPerm.repo_to_perm_id')
1414 order_by='UserRepoToPerm.repo_to_perm_id')
1418 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1415 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1419 stats = relationship('Statistics', cascade='all', uselist=False)
1416 stats = relationship('Statistics', cascade='all', uselist=False)
1420
1417
1421 followers = relationship(
1418 followers = relationship(
1422 'UserFollowing',
1419 'UserFollowing',
1423 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1420 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1424 cascade='all')
1421 cascade='all')
1425 extra_fields = relationship(
1422 extra_fields = relationship(
1426 'RepositoryField', cascade="all, delete, delete-orphan")
1423 'RepositoryField', cascade="all, delete, delete-orphan")
1427 logs = relationship('UserLog')
1424 logs = relationship('UserLog')
1428 comments = relationship(
1425 comments = relationship(
1429 'ChangesetComment', cascade="all, delete, delete-orphan")
1426 'ChangesetComment', cascade="all, delete, delete-orphan")
1430 pull_requests_source = relationship(
1427 pull_requests_source = relationship(
1431 'PullRequest',
1428 'PullRequest',
1432 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1429 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1433 cascade="all, delete, delete-orphan")
1430 cascade="all, delete, delete-orphan")
1434 pull_requests_target = relationship(
1431 pull_requests_target = relationship(
1435 'PullRequest',
1432 'PullRequest',
1436 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1433 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1437 cascade="all, delete, delete-orphan")
1434 cascade="all, delete, delete-orphan")
1438 ui = relationship('RepoRhodeCodeUi', cascade="all")
1435 ui = relationship('RepoRhodeCodeUi', cascade="all")
1439 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1436 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1440 integrations = relationship('Integration',
1437 integrations = relationship('Integration',
1441 cascade="all, delete, delete-orphan")
1438 cascade="all, delete, delete-orphan")
1442
1439
1443 def __unicode__(self):
1440 def __unicode__(self):
1444 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1441 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1445 safe_unicode(self.repo_name))
1442 safe_unicode(self.repo_name))
1446
1443
1447 @hybrid_property
1444 @hybrid_property
1448 def landing_rev(self):
1445 def landing_rev(self):
1449 # always should return [rev_type, rev]
1446 # always should return [rev_type, rev]
1450 if self._landing_revision:
1447 if self._landing_revision:
1451 _rev_info = self._landing_revision.split(':')
1448 _rev_info = self._landing_revision.split(':')
1452 if len(_rev_info) < 2:
1449 if len(_rev_info) < 2:
1453 _rev_info.insert(0, 'rev')
1450 _rev_info.insert(0, 'rev')
1454 return [_rev_info[0], _rev_info[1]]
1451 return [_rev_info[0], _rev_info[1]]
1455 return [None, None]
1452 return [None, None]
1456
1453
1457 @landing_rev.setter
1454 @landing_rev.setter
1458 def landing_rev(self, val):
1455 def landing_rev(self, val):
1459 if ':' not in val:
1456 if ':' not in val:
1460 raise ValueError('value must be delimited with `:` and consist '
1457 raise ValueError('value must be delimited with `:` and consist '
1461 'of <rev_type>:<rev>, got %s instead' % val)
1458 'of <rev_type>:<rev>, got %s instead' % val)
1462 self._landing_revision = val
1459 self._landing_revision = val
1463
1460
1464 @hybrid_property
1461 @hybrid_property
1465 def locked(self):
1462 def locked(self):
1466 if self._locked:
1463 if self._locked:
1467 user_id, timelocked, reason = self._locked.split(':')
1464 user_id, timelocked, reason = self._locked.split(':')
1468 lock_values = int(user_id), timelocked, reason
1465 lock_values = int(user_id), timelocked, reason
1469 else:
1466 else:
1470 lock_values = [None, None, None]
1467 lock_values = [None, None, None]
1471 return lock_values
1468 return lock_values
1472
1469
1473 @locked.setter
1470 @locked.setter
1474 def locked(self, val):
1471 def locked(self, val):
1475 if val and isinstance(val, (list, tuple)):
1472 if val and isinstance(val, (list, tuple)):
1476 self._locked = ':'.join(map(str, val))
1473 self._locked = ':'.join(map(str, val))
1477 else:
1474 else:
1478 self._locked = None
1475 self._locked = None
1479
1476
1480 @hybrid_property
1477 @hybrid_property
1481 def changeset_cache(self):
1478 def changeset_cache(self):
1482 from rhodecode.lib.vcs.backends.base import EmptyCommit
1479 from rhodecode.lib.vcs.backends.base import EmptyCommit
1483 dummy = EmptyCommit().__json__()
1480 dummy = EmptyCommit().__json__()
1484 if not self._changeset_cache:
1481 if not self._changeset_cache:
1485 return dummy
1482 return dummy
1486 try:
1483 try:
1487 return json.loads(self._changeset_cache)
1484 return json.loads(self._changeset_cache)
1488 except TypeError:
1485 except TypeError:
1489 return dummy
1486 return dummy
1490 except Exception:
1487 except Exception:
1491 log.error(traceback.format_exc())
1488 log.error(traceback.format_exc())
1492 return dummy
1489 return dummy
1493
1490
1494 @changeset_cache.setter
1491 @changeset_cache.setter
1495 def changeset_cache(self, val):
1492 def changeset_cache(self, val):
1496 try:
1493 try:
1497 self._changeset_cache = json.dumps(val)
1494 self._changeset_cache = json.dumps(val)
1498 except Exception:
1495 except Exception:
1499 log.error(traceback.format_exc())
1496 log.error(traceback.format_exc())
1500
1497
1501 @hybrid_property
1498 @hybrid_property
1502 def repo_name(self):
1499 def repo_name(self):
1503 return self._repo_name
1500 return self._repo_name
1504
1501
1505 @repo_name.setter
1502 @repo_name.setter
1506 def repo_name(self, value):
1503 def repo_name(self, value):
1507 self._repo_name = value
1504 self._repo_name = value
1508 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1505 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1509
1506
1510 @classmethod
1507 @classmethod
1511 def normalize_repo_name(cls, repo_name):
1508 def normalize_repo_name(cls, repo_name):
1512 """
1509 """
1513 Normalizes os specific repo_name to the format internally stored inside
1510 Normalizes os specific repo_name to the format internally stored inside
1514 database using URL_SEP
1511 database using URL_SEP
1515
1512
1516 :param cls:
1513 :param cls:
1517 :param repo_name:
1514 :param repo_name:
1518 """
1515 """
1519 return cls.NAME_SEP.join(repo_name.split(os.sep))
1516 return cls.NAME_SEP.join(repo_name.split(os.sep))
1520
1517
1521 @classmethod
1518 @classmethod
1522 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1519 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1523 session = Session()
1520 session = Session()
1524 q = session.query(cls).filter(cls.repo_name == repo_name)
1521 q = session.query(cls).filter(cls.repo_name == repo_name)
1525
1522
1526 if cache:
1523 if cache:
1527 if identity_cache:
1524 if identity_cache:
1528 val = cls.identity_cache(session, 'repo_name', repo_name)
1525 val = cls.identity_cache(session, 'repo_name', repo_name)
1529 if val:
1526 if val:
1530 return val
1527 return val
1531 else:
1528 else:
1532 q = q.options(
1529 q = q.options(
1533 FromCache("sql_cache_short",
1530 FromCache("sql_cache_short",
1534 "get_repo_by_name_%s" % _hash_key(repo_name)))
1531 "get_repo_by_name_%s" % _hash_key(repo_name)))
1535
1532
1536 return q.scalar()
1533 return q.scalar()
1537
1534
1538 @classmethod
1535 @classmethod
1539 def get_by_full_path(cls, repo_full_path):
1536 def get_by_full_path(cls, repo_full_path):
1540 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1537 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1541 repo_name = cls.normalize_repo_name(repo_name)
1538 repo_name = cls.normalize_repo_name(repo_name)
1542 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1539 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1543
1540
1544 @classmethod
1541 @classmethod
1545 def get_repo_forks(cls, repo_id):
1542 def get_repo_forks(cls, repo_id):
1546 return cls.query().filter(Repository.fork_id == repo_id)
1543 return cls.query().filter(Repository.fork_id == repo_id)
1547
1544
1548 @classmethod
1545 @classmethod
1549 def base_path(cls):
1546 def base_path(cls):
1550 """
1547 """
1551 Returns base path when all repos are stored
1548 Returns base path when all repos are stored
1552
1549
1553 :param cls:
1550 :param cls:
1554 """
1551 """
1555 q = Session().query(RhodeCodeUi)\
1552 q = Session().query(RhodeCodeUi)\
1556 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1553 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1557 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1554 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1558 return q.one().ui_value
1555 return q.one().ui_value
1559
1556
1560 @classmethod
1557 @classmethod
1561 def is_valid(cls, repo_name):
1558 def is_valid(cls, repo_name):
1562 """
1559 """
1563 returns True if given repo name is a valid filesystem repository
1560 returns True if given repo name is a valid filesystem repository
1564
1561
1565 :param cls:
1562 :param cls:
1566 :param repo_name:
1563 :param repo_name:
1567 """
1564 """
1568 from rhodecode.lib.utils import is_valid_repo
1565 from rhodecode.lib.utils import is_valid_repo
1569
1566
1570 return is_valid_repo(repo_name, cls.base_path())
1567 return is_valid_repo(repo_name, cls.base_path())
1571
1568
1572 @classmethod
1569 @classmethod
1573 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1570 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1574 case_insensitive=True):
1571 case_insensitive=True):
1575 q = Repository.query()
1572 q = Repository.query()
1576
1573
1577 if not isinstance(user_id, Optional):
1574 if not isinstance(user_id, Optional):
1578 q = q.filter(Repository.user_id == user_id)
1575 q = q.filter(Repository.user_id == user_id)
1579
1576
1580 if not isinstance(group_id, Optional):
1577 if not isinstance(group_id, Optional):
1581 q = q.filter(Repository.group_id == group_id)
1578 q = q.filter(Repository.group_id == group_id)
1582
1579
1583 if case_insensitive:
1580 if case_insensitive:
1584 q = q.order_by(func.lower(Repository.repo_name))
1581 q = q.order_by(func.lower(Repository.repo_name))
1585 else:
1582 else:
1586 q = q.order_by(Repository.repo_name)
1583 q = q.order_by(Repository.repo_name)
1587 return q.all()
1584 return q.all()
1588
1585
1589 @property
1586 @property
1590 def forks(self):
1587 def forks(self):
1591 """
1588 """
1592 Return forks of this repo
1589 Return forks of this repo
1593 """
1590 """
1594 return Repository.get_repo_forks(self.repo_id)
1591 return Repository.get_repo_forks(self.repo_id)
1595
1592
1596 @property
1593 @property
1597 def parent(self):
1594 def parent(self):
1598 """
1595 """
1599 Returns fork parent
1596 Returns fork parent
1600 """
1597 """
1601 return self.fork
1598 return self.fork
1602
1599
1603 @property
1600 @property
1604 def just_name(self):
1601 def just_name(self):
1605 return self.repo_name.split(self.NAME_SEP)[-1]
1602 return self.repo_name.split(self.NAME_SEP)[-1]
1606
1603
1607 @property
1604 @property
1608 def groups_with_parents(self):
1605 def groups_with_parents(self):
1609 groups = []
1606 groups = []
1610 if self.group is None:
1607 if self.group is None:
1611 return groups
1608 return groups
1612
1609
1613 cur_gr = self.group
1610 cur_gr = self.group
1614 groups.insert(0, cur_gr)
1611 groups.insert(0, cur_gr)
1615 while 1:
1612 while 1:
1616 gr = getattr(cur_gr, 'parent_group', None)
1613 gr = getattr(cur_gr, 'parent_group', None)
1617 cur_gr = cur_gr.parent_group
1614 cur_gr = cur_gr.parent_group
1618 if gr is None:
1615 if gr is None:
1619 break
1616 break
1620 groups.insert(0, gr)
1617 groups.insert(0, gr)
1621
1618
1622 return groups
1619 return groups
1623
1620
1624 @property
1621 @property
1625 def groups_and_repo(self):
1622 def groups_and_repo(self):
1626 return self.groups_with_parents, self
1623 return self.groups_with_parents, self
1627
1624
1628 @LazyProperty
1625 @LazyProperty
1629 def repo_path(self):
1626 def repo_path(self):
1630 """
1627 """
1631 Returns base full path for that repository means where it actually
1628 Returns base full path for that repository means where it actually
1632 exists on a filesystem
1629 exists on a filesystem
1633 """
1630 """
1634 q = Session().query(RhodeCodeUi).filter(
1631 q = Session().query(RhodeCodeUi).filter(
1635 RhodeCodeUi.ui_key == self.NAME_SEP)
1632 RhodeCodeUi.ui_key == self.NAME_SEP)
1636 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1633 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1637 return q.one().ui_value
1634 return q.one().ui_value
1638
1635
1639 @property
1636 @property
1640 def repo_full_path(self):
1637 def repo_full_path(self):
1641 p = [self.repo_path]
1638 p = [self.repo_path]
1642 # we need to split the name by / since this is how we store the
1639 # we need to split the name by / since this is how we store the
1643 # names in the database, but that eventually needs to be converted
1640 # names in the database, but that eventually needs to be converted
1644 # into a valid system path
1641 # into a valid system path
1645 p += self.repo_name.split(self.NAME_SEP)
1642 p += self.repo_name.split(self.NAME_SEP)
1646 return os.path.join(*map(safe_unicode, p))
1643 return os.path.join(*map(safe_unicode, p))
1647
1644
1648 @property
1645 @property
1649 def cache_keys(self):
1646 def cache_keys(self):
1650 """
1647 """
1651 Returns associated cache keys for that repo
1648 Returns associated cache keys for that repo
1652 """
1649 """
1653 return CacheKey.query()\
1650 return CacheKey.query()\
1654 .filter(CacheKey.cache_args == self.repo_name)\
1651 .filter(CacheKey.cache_args == self.repo_name)\
1655 .order_by(CacheKey.cache_key)\
1652 .order_by(CacheKey.cache_key)\
1656 .all()
1653 .all()
1657
1654
1658 def get_new_name(self, repo_name):
1655 def get_new_name(self, repo_name):
1659 """
1656 """
1660 returns new full repository name based on assigned group and new new
1657 returns new full repository name based on assigned group and new new
1661
1658
1662 :param group_name:
1659 :param group_name:
1663 """
1660 """
1664 path_prefix = self.group.full_path_splitted if self.group else []
1661 path_prefix = self.group.full_path_splitted if self.group else []
1665 return self.NAME_SEP.join(path_prefix + [repo_name])
1662 return self.NAME_SEP.join(path_prefix + [repo_name])
1666
1663
1667 @property
1664 @property
1668 def _config(self):
1665 def _config(self):
1669 """
1666 """
1670 Returns db based config object.
1667 Returns db based config object.
1671 """
1668 """
1672 from rhodecode.lib.utils import make_db_config
1669 from rhodecode.lib.utils import make_db_config
1673 return make_db_config(clear_session=False, repo=self)
1670 return make_db_config(clear_session=False, repo=self)
1674
1671
1675 def permissions(self, with_admins=True, with_owner=True):
1672 def permissions(self, with_admins=True, with_owner=True):
1676 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1673 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1677 q = q.options(joinedload(UserRepoToPerm.repository),
1674 q = q.options(joinedload(UserRepoToPerm.repository),
1678 joinedload(UserRepoToPerm.user),
1675 joinedload(UserRepoToPerm.user),
1679 joinedload(UserRepoToPerm.permission),)
1676 joinedload(UserRepoToPerm.permission),)
1680
1677
1681 # get owners and admins and permissions. We do a trick of re-writing
1678 # get owners and admins and permissions. We do a trick of re-writing
1682 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1679 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1683 # has a global reference and changing one object propagates to all
1680 # has a global reference and changing one object propagates to all
1684 # others. This means if admin is also an owner admin_row that change
1681 # others. This means if admin is also an owner admin_row that change
1685 # would propagate to both objects
1682 # would propagate to both objects
1686 perm_rows = []
1683 perm_rows = []
1687 for _usr in q.all():
1684 for _usr in q.all():
1688 usr = AttributeDict(_usr.user.get_dict())
1685 usr = AttributeDict(_usr.user.get_dict())
1689 usr.permission = _usr.permission.permission_name
1686 usr.permission = _usr.permission.permission_name
1690 perm_rows.append(usr)
1687 perm_rows.append(usr)
1691
1688
1692 # filter the perm rows by 'default' first and then sort them by
1689 # filter the perm rows by 'default' first and then sort them by
1693 # admin,write,read,none permissions sorted again alphabetically in
1690 # admin,write,read,none permissions sorted again alphabetically in
1694 # each group
1691 # each group
1695 perm_rows = sorted(perm_rows, key=display_sort)
1692 perm_rows = sorted(perm_rows, key=display_sort)
1696
1693
1697 _admin_perm = 'repository.admin'
1694 _admin_perm = 'repository.admin'
1698 owner_row = []
1695 owner_row = []
1699 if with_owner:
1696 if with_owner:
1700 usr = AttributeDict(self.user.get_dict())
1697 usr = AttributeDict(self.user.get_dict())
1701 usr.owner_row = True
1698 usr.owner_row = True
1702 usr.permission = _admin_perm
1699 usr.permission = _admin_perm
1703 owner_row.append(usr)
1700 owner_row.append(usr)
1704
1701
1705 super_admin_rows = []
1702 super_admin_rows = []
1706 if with_admins:
1703 if with_admins:
1707 for usr in User.get_all_super_admins():
1704 for usr in User.get_all_super_admins():
1708 # if this admin is also owner, don't double the record
1705 # if this admin is also owner, don't double the record
1709 if usr.user_id == owner_row[0].user_id:
1706 if usr.user_id == owner_row[0].user_id:
1710 owner_row[0].admin_row = True
1707 owner_row[0].admin_row = True
1711 else:
1708 else:
1712 usr = AttributeDict(usr.get_dict())
1709 usr = AttributeDict(usr.get_dict())
1713 usr.admin_row = True
1710 usr.admin_row = True
1714 usr.permission = _admin_perm
1711 usr.permission = _admin_perm
1715 super_admin_rows.append(usr)
1712 super_admin_rows.append(usr)
1716
1713
1717 return super_admin_rows + owner_row + perm_rows
1714 return super_admin_rows + owner_row + perm_rows
1718
1715
1719 def permission_user_groups(self):
1716 def permission_user_groups(self):
1720 q = UserGroupRepoToPerm.query().filter(
1717 q = UserGroupRepoToPerm.query().filter(
1721 UserGroupRepoToPerm.repository == self)
1718 UserGroupRepoToPerm.repository == self)
1722 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1719 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1723 joinedload(UserGroupRepoToPerm.users_group),
1720 joinedload(UserGroupRepoToPerm.users_group),
1724 joinedload(UserGroupRepoToPerm.permission),)
1721 joinedload(UserGroupRepoToPerm.permission),)
1725
1722
1726 perm_rows = []
1723 perm_rows = []
1727 for _user_group in q.all():
1724 for _user_group in q.all():
1728 usr = AttributeDict(_user_group.users_group.get_dict())
1725 usr = AttributeDict(_user_group.users_group.get_dict())
1729 usr.permission = _user_group.permission.permission_name
1726 usr.permission = _user_group.permission.permission_name
1730 perm_rows.append(usr)
1727 perm_rows.append(usr)
1731
1728
1732 return perm_rows
1729 return perm_rows
1733
1730
1734 def get_api_data(self, include_secrets=False):
1731 def get_api_data(self, include_secrets=False):
1735 """
1732 """
1736 Common function for generating repo api data
1733 Common function for generating repo api data
1737
1734
1738 :param include_secrets: See :meth:`User.get_api_data`.
1735 :param include_secrets: See :meth:`User.get_api_data`.
1739
1736
1740 """
1737 """
1741 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1738 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1742 # move this methods on models level.
1739 # move this methods on models level.
1743 from rhodecode.model.settings import SettingsModel
1740 from rhodecode.model.settings import SettingsModel
1744
1741
1745 repo = self
1742 repo = self
1746 _user_id, _time, _reason = self.locked
1743 _user_id, _time, _reason = self.locked
1747
1744
1748 data = {
1745 data = {
1749 'repo_id': repo.repo_id,
1746 'repo_id': repo.repo_id,
1750 'repo_name': repo.repo_name,
1747 'repo_name': repo.repo_name,
1751 'repo_type': repo.repo_type,
1748 'repo_type': repo.repo_type,
1752 'clone_uri': repo.clone_uri or '',
1749 'clone_uri': repo.clone_uri or '',
1753 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1750 'url': url('summary_home', repo_name=self.repo_name, qualified=True),
1754 'private': repo.private,
1751 'private': repo.private,
1755 'created_on': repo.created_on,
1752 'created_on': repo.created_on,
1756 'description': repo.description,
1753 'description': repo.description,
1757 'landing_rev': repo.landing_rev,
1754 'landing_rev': repo.landing_rev,
1758 'owner': repo.user.username,
1755 'owner': repo.user.username,
1759 'fork_of': repo.fork.repo_name if repo.fork else None,
1756 'fork_of': repo.fork.repo_name if repo.fork else None,
1760 'enable_statistics': repo.enable_statistics,
1757 'enable_statistics': repo.enable_statistics,
1761 'enable_locking': repo.enable_locking,
1758 'enable_locking': repo.enable_locking,
1762 'enable_downloads': repo.enable_downloads,
1759 'enable_downloads': repo.enable_downloads,
1763 'last_changeset': repo.changeset_cache,
1760 'last_changeset': repo.changeset_cache,
1764 'locked_by': User.get(_user_id).get_api_data(
1761 'locked_by': User.get(_user_id).get_api_data(
1765 include_secrets=include_secrets) if _user_id else None,
1762 include_secrets=include_secrets) if _user_id else None,
1766 'locked_date': time_to_datetime(_time) if _time else None,
1763 'locked_date': time_to_datetime(_time) if _time else None,
1767 'lock_reason': _reason if _reason else None,
1764 'lock_reason': _reason if _reason else None,
1768 }
1765 }
1769
1766
1770 # TODO: mikhail: should be per-repo settings here
1767 # TODO: mikhail: should be per-repo settings here
1771 rc_config = SettingsModel().get_all_settings()
1768 rc_config = SettingsModel().get_all_settings()
1772 repository_fields = str2bool(
1769 repository_fields = str2bool(
1773 rc_config.get('rhodecode_repository_fields'))
1770 rc_config.get('rhodecode_repository_fields'))
1774 if repository_fields:
1771 if repository_fields:
1775 for f in self.extra_fields:
1772 for f in self.extra_fields:
1776 data[f.field_key_prefixed] = f.field_value
1773 data[f.field_key_prefixed] = f.field_value
1777
1774
1778 return data
1775 return data
1779
1776
1780 @classmethod
1777 @classmethod
1781 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1778 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1782 if not lock_time:
1779 if not lock_time:
1783 lock_time = time.time()
1780 lock_time = time.time()
1784 if not lock_reason:
1781 if not lock_reason:
1785 lock_reason = cls.LOCK_AUTOMATIC
1782 lock_reason = cls.LOCK_AUTOMATIC
1786 repo.locked = [user_id, lock_time, lock_reason]
1783 repo.locked = [user_id, lock_time, lock_reason]
1787 Session().add(repo)
1784 Session().add(repo)
1788 Session().commit()
1785 Session().commit()
1789
1786
1790 @classmethod
1787 @classmethod
1791 def unlock(cls, repo):
1788 def unlock(cls, repo):
1792 repo.locked = None
1789 repo.locked = None
1793 Session().add(repo)
1790 Session().add(repo)
1794 Session().commit()
1791 Session().commit()
1795
1792
1796 @classmethod
1793 @classmethod
1797 def getlock(cls, repo):
1794 def getlock(cls, repo):
1798 return repo.locked
1795 return repo.locked
1799
1796
1800 def is_user_lock(self, user_id):
1797 def is_user_lock(self, user_id):
1801 if self.lock[0]:
1798 if self.lock[0]:
1802 lock_user_id = safe_int(self.lock[0])
1799 lock_user_id = safe_int(self.lock[0])
1803 user_id = safe_int(user_id)
1800 user_id = safe_int(user_id)
1804 # both are ints, and they are equal
1801 # both are ints, and they are equal
1805 return all([lock_user_id, user_id]) and lock_user_id == user_id
1802 return all([lock_user_id, user_id]) and lock_user_id == user_id
1806
1803
1807 return False
1804 return False
1808
1805
1809 def get_locking_state(self, action, user_id, only_when_enabled=True):
1806 def get_locking_state(self, action, user_id, only_when_enabled=True):
1810 """
1807 """
1811 Checks locking on this repository, if locking is enabled and lock is
1808 Checks locking on this repository, if locking is enabled and lock is
1812 present returns a tuple of make_lock, locked, locked_by.
1809 present returns a tuple of make_lock, locked, locked_by.
1813 make_lock can have 3 states None (do nothing) True, make lock
1810 make_lock can have 3 states None (do nothing) True, make lock
1814 False release lock, This value is later propagated to hooks, which
1811 False release lock, This value is later propagated to hooks, which
1815 do the locking. Think about this as signals passed to hooks what to do.
1812 do the locking. Think about this as signals passed to hooks what to do.
1816
1813
1817 """
1814 """
1818 # TODO: johbo: This is part of the business logic and should be moved
1815 # TODO: johbo: This is part of the business logic and should be moved
1819 # into the RepositoryModel.
1816 # into the RepositoryModel.
1820
1817
1821 if action not in ('push', 'pull'):
1818 if action not in ('push', 'pull'):
1822 raise ValueError("Invalid action value: %s" % repr(action))
1819 raise ValueError("Invalid action value: %s" % repr(action))
1823
1820
1824 # defines if locked error should be thrown to user
1821 # defines if locked error should be thrown to user
1825 currently_locked = False
1822 currently_locked = False
1826 # defines if new lock should be made, tri-state
1823 # defines if new lock should be made, tri-state
1827 make_lock = None
1824 make_lock = None
1828 repo = self
1825 repo = self
1829 user = User.get(user_id)
1826 user = User.get(user_id)
1830
1827
1831 lock_info = repo.locked
1828 lock_info = repo.locked
1832
1829
1833 if repo and (repo.enable_locking or not only_when_enabled):
1830 if repo and (repo.enable_locking or not only_when_enabled):
1834 if action == 'push':
1831 if action == 'push':
1835 # check if it's already locked !, if it is compare users
1832 # check if it's already locked !, if it is compare users
1836 locked_by_user_id = lock_info[0]
1833 locked_by_user_id = lock_info[0]
1837 if user.user_id == locked_by_user_id:
1834 if user.user_id == locked_by_user_id:
1838 log.debug(
1835 log.debug(
1839 'Got `push` action from user %s, now unlocking', user)
1836 'Got `push` action from user %s, now unlocking', user)
1840 # unlock if we have push from user who locked
1837 # unlock if we have push from user who locked
1841 make_lock = False
1838 make_lock = False
1842 else:
1839 else:
1843 # we're not the same user who locked, ban with
1840 # we're not the same user who locked, ban with
1844 # code defined in settings (default is 423 HTTP Locked) !
1841 # code defined in settings (default is 423 HTTP Locked) !
1845 log.debug('Repo %s is currently locked by %s', repo, user)
1842 log.debug('Repo %s is currently locked by %s', repo, user)
1846 currently_locked = True
1843 currently_locked = True
1847 elif action == 'pull':
1844 elif action == 'pull':
1848 # [0] user [1] date
1845 # [0] user [1] date
1849 if lock_info[0] and lock_info[1]:
1846 if lock_info[0] and lock_info[1]:
1850 log.debug('Repo %s is currently locked by %s', repo, user)
1847 log.debug('Repo %s is currently locked by %s', repo, user)
1851 currently_locked = True
1848 currently_locked = True
1852 else:
1849 else:
1853 log.debug('Setting lock on repo %s by %s', repo, user)
1850 log.debug('Setting lock on repo %s by %s', repo, user)
1854 make_lock = True
1851 make_lock = True
1855
1852
1856 else:
1853 else:
1857 log.debug('Repository %s do not have locking enabled', repo)
1854 log.debug('Repository %s do not have locking enabled', repo)
1858
1855
1859 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1856 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
1860 make_lock, currently_locked, lock_info)
1857 make_lock, currently_locked, lock_info)
1861
1858
1862 from rhodecode.lib.auth import HasRepoPermissionAny
1859 from rhodecode.lib.auth import HasRepoPermissionAny
1863 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1860 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
1864 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1861 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
1865 # if we don't have at least write permission we cannot make a lock
1862 # if we don't have at least write permission we cannot make a lock
1866 log.debug('lock state reset back to FALSE due to lack '
1863 log.debug('lock state reset back to FALSE due to lack '
1867 'of at least read permission')
1864 'of at least read permission')
1868 make_lock = False
1865 make_lock = False
1869
1866
1870 return make_lock, currently_locked, lock_info
1867 return make_lock, currently_locked, lock_info
1871
1868
1872 @property
1869 @property
1873 def last_db_change(self):
1870 def last_db_change(self):
1874 return self.updated_on
1871 return self.updated_on
1875
1872
1876 @property
1873 @property
1877 def clone_uri_hidden(self):
1874 def clone_uri_hidden(self):
1878 clone_uri = self.clone_uri
1875 clone_uri = self.clone_uri
1879 if clone_uri:
1876 if clone_uri:
1880 import urlobject
1877 import urlobject
1881 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1878 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
1882 if url_obj.password:
1879 if url_obj.password:
1883 clone_uri = url_obj.with_password('*****')
1880 clone_uri = url_obj.with_password('*****')
1884 return clone_uri
1881 return clone_uri
1885
1882
1886 def clone_url(self, **override):
1883 def clone_url(self, **override):
1887 qualified_home_url = url('home', qualified=True)
1884 qualified_home_url = url('home', qualified=True)
1888
1885
1889 uri_tmpl = None
1886 uri_tmpl = None
1890 if 'with_id' in override:
1887 if 'with_id' in override:
1891 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1888 uri_tmpl = self.DEFAULT_CLONE_URI_ID
1892 del override['with_id']
1889 del override['with_id']
1893
1890
1894 if 'uri_tmpl' in override:
1891 if 'uri_tmpl' in override:
1895 uri_tmpl = override['uri_tmpl']
1892 uri_tmpl = override['uri_tmpl']
1896 del override['uri_tmpl']
1893 del override['uri_tmpl']
1897
1894
1898 # we didn't override our tmpl from **overrides
1895 # we didn't override our tmpl from **overrides
1899 if not uri_tmpl:
1896 if not uri_tmpl:
1900 uri_tmpl = self.DEFAULT_CLONE_URI
1897 uri_tmpl = self.DEFAULT_CLONE_URI
1901 try:
1898 try:
1902 from pylons import tmpl_context as c
1899 from pylons import tmpl_context as c
1903 uri_tmpl = c.clone_uri_tmpl
1900 uri_tmpl = c.clone_uri_tmpl
1904 except Exception:
1901 except Exception:
1905 # in any case if we call this outside of request context,
1902 # in any case if we call this outside of request context,
1906 # ie, not having tmpl_context set up
1903 # ie, not having tmpl_context set up
1907 pass
1904 pass
1908
1905
1909 return get_clone_url(uri_tmpl=uri_tmpl,
1906 return get_clone_url(uri_tmpl=uri_tmpl,
1910 qualifed_home_url=qualified_home_url,
1907 qualifed_home_url=qualified_home_url,
1911 repo_name=self.repo_name,
1908 repo_name=self.repo_name,
1912 repo_id=self.repo_id, **override)
1909 repo_id=self.repo_id, **override)
1913
1910
1914 def set_state(self, state):
1911 def set_state(self, state):
1915 self.repo_state = state
1912 self.repo_state = state
1916 Session().add(self)
1913 Session().add(self)
1917 #==========================================================================
1914 #==========================================================================
1918 # SCM PROPERTIES
1915 # SCM PROPERTIES
1919 #==========================================================================
1916 #==========================================================================
1920
1917
1921 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1918 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
1922 return get_commit_safe(
1919 return get_commit_safe(
1923 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1920 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
1924
1921
1925 def get_changeset(self, rev=None, pre_load=None):
1922 def get_changeset(self, rev=None, pre_load=None):
1926 warnings.warn("Use get_commit", DeprecationWarning)
1923 warnings.warn("Use get_commit", DeprecationWarning)
1927 commit_id = None
1924 commit_id = None
1928 commit_idx = None
1925 commit_idx = None
1929 if isinstance(rev, basestring):
1926 if isinstance(rev, basestring):
1930 commit_id = rev
1927 commit_id = rev
1931 else:
1928 else:
1932 commit_idx = rev
1929 commit_idx = rev
1933 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1930 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
1934 pre_load=pre_load)
1931 pre_load=pre_load)
1935
1932
1936 def get_landing_commit(self):
1933 def get_landing_commit(self):
1937 """
1934 """
1938 Returns landing commit, or if that doesn't exist returns the tip
1935 Returns landing commit, or if that doesn't exist returns the tip
1939 """
1936 """
1940 _rev_type, _rev = self.landing_rev
1937 _rev_type, _rev = self.landing_rev
1941 commit = self.get_commit(_rev)
1938 commit = self.get_commit(_rev)
1942 if isinstance(commit, EmptyCommit):
1939 if isinstance(commit, EmptyCommit):
1943 return self.get_commit()
1940 return self.get_commit()
1944 return commit
1941 return commit
1945
1942
1946 def update_commit_cache(self, cs_cache=None, config=None):
1943 def update_commit_cache(self, cs_cache=None, config=None):
1947 """
1944 """
1948 Update cache of last changeset for repository, keys should be::
1945 Update cache of last changeset for repository, keys should be::
1949
1946
1950 short_id
1947 short_id
1951 raw_id
1948 raw_id
1952 revision
1949 revision
1953 parents
1950 parents
1954 message
1951 message
1955 date
1952 date
1956 author
1953 author
1957
1954
1958 :param cs_cache:
1955 :param cs_cache:
1959 """
1956 """
1960 from rhodecode.lib.vcs.backends.base import BaseChangeset
1957 from rhodecode.lib.vcs.backends.base import BaseChangeset
1961 if cs_cache is None:
1958 if cs_cache is None:
1962 # use no-cache version here
1959 # use no-cache version here
1963 scm_repo = self.scm_instance(cache=False, config=config)
1960 scm_repo = self.scm_instance(cache=False, config=config)
1964 if scm_repo:
1961 if scm_repo:
1965 cs_cache = scm_repo.get_commit(
1962 cs_cache = scm_repo.get_commit(
1966 pre_load=["author", "date", "message", "parents"])
1963 pre_load=["author", "date", "message", "parents"])
1967 else:
1964 else:
1968 cs_cache = EmptyCommit()
1965 cs_cache = EmptyCommit()
1969
1966
1970 if isinstance(cs_cache, BaseChangeset):
1967 if isinstance(cs_cache, BaseChangeset):
1971 cs_cache = cs_cache.__json__()
1968 cs_cache = cs_cache.__json__()
1972
1969
1973 def is_outdated(new_cs_cache):
1970 def is_outdated(new_cs_cache):
1974 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1971 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
1975 new_cs_cache['revision'] != self.changeset_cache['revision']):
1972 new_cs_cache['revision'] != self.changeset_cache['revision']):
1976 return True
1973 return True
1977 return False
1974 return False
1978
1975
1979 # check if we have maybe already latest cached revision
1976 # check if we have maybe already latest cached revision
1980 if is_outdated(cs_cache) or not self.changeset_cache:
1977 if is_outdated(cs_cache) or not self.changeset_cache:
1981 _default = datetime.datetime.fromtimestamp(0)
1978 _default = datetime.datetime.fromtimestamp(0)
1982 last_change = cs_cache.get('date') or _default
1979 last_change = cs_cache.get('date') or _default
1983 log.debug('updated repo %s with new cs cache %s',
1980 log.debug('updated repo %s with new cs cache %s',
1984 self.repo_name, cs_cache)
1981 self.repo_name, cs_cache)
1985 self.updated_on = last_change
1982 self.updated_on = last_change
1986 self.changeset_cache = cs_cache
1983 self.changeset_cache = cs_cache
1987 Session().add(self)
1984 Session().add(self)
1988 Session().commit()
1985 Session().commit()
1989 else:
1986 else:
1990 log.debug('Skipping update_commit_cache for repo:`%s` '
1987 log.debug('Skipping update_commit_cache for repo:`%s` '
1991 'commit already with latest changes', self.repo_name)
1988 'commit already with latest changes', self.repo_name)
1992
1989
1993 @property
1990 @property
1994 def tip(self):
1991 def tip(self):
1995 return self.get_commit('tip')
1992 return self.get_commit('tip')
1996
1993
1997 @property
1994 @property
1998 def author(self):
1995 def author(self):
1999 return self.tip.author
1996 return self.tip.author
2000
1997
2001 @property
1998 @property
2002 def last_change(self):
1999 def last_change(self):
2003 return self.scm_instance().last_change
2000 return self.scm_instance().last_change
2004
2001
2005 def get_comments(self, revisions=None):
2002 def get_comments(self, revisions=None):
2006 """
2003 """
2007 Returns comments for this repository grouped by revisions
2004 Returns comments for this repository grouped by revisions
2008
2005
2009 :param revisions: filter query by revisions only
2006 :param revisions: filter query by revisions only
2010 """
2007 """
2011 cmts = ChangesetComment.query()\
2008 cmts = ChangesetComment.query()\
2012 .filter(ChangesetComment.repo == self)
2009 .filter(ChangesetComment.repo == self)
2013 if revisions:
2010 if revisions:
2014 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2011 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2015 grouped = collections.defaultdict(list)
2012 grouped = collections.defaultdict(list)
2016 for cmt in cmts.all():
2013 for cmt in cmts.all():
2017 grouped[cmt.revision].append(cmt)
2014 grouped[cmt.revision].append(cmt)
2018 return grouped
2015 return grouped
2019
2016
2020 def statuses(self, revisions=None):
2017 def statuses(self, revisions=None):
2021 """
2018 """
2022 Returns statuses for this repository
2019 Returns statuses for this repository
2023
2020
2024 :param revisions: list of revisions to get statuses for
2021 :param revisions: list of revisions to get statuses for
2025 """
2022 """
2026 statuses = ChangesetStatus.query()\
2023 statuses = ChangesetStatus.query()\
2027 .filter(ChangesetStatus.repo == self)\
2024 .filter(ChangesetStatus.repo == self)\
2028 .filter(ChangesetStatus.version == 0)
2025 .filter(ChangesetStatus.version == 0)
2029
2026
2030 if revisions:
2027 if revisions:
2031 # Try doing the filtering in chunks to avoid hitting limits
2028 # Try doing the filtering in chunks to avoid hitting limits
2032 size = 500
2029 size = 500
2033 status_results = []
2030 status_results = []
2034 for chunk in xrange(0, len(revisions), size):
2031 for chunk in xrange(0, len(revisions), size):
2035 status_results += statuses.filter(
2032 status_results += statuses.filter(
2036 ChangesetStatus.revision.in_(
2033 ChangesetStatus.revision.in_(
2037 revisions[chunk: chunk+size])
2034 revisions[chunk: chunk+size])
2038 ).all()
2035 ).all()
2039 else:
2036 else:
2040 status_results = statuses.all()
2037 status_results = statuses.all()
2041
2038
2042 grouped = {}
2039 grouped = {}
2043
2040
2044 # maybe we have open new pullrequest without a status?
2041 # maybe we have open new pullrequest without a status?
2045 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2042 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2046 status_lbl = ChangesetStatus.get_status_lbl(stat)
2043 status_lbl = ChangesetStatus.get_status_lbl(stat)
2047 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2044 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2048 for rev in pr.revisions:
2045 for rev in pr.revisions:
2049 pr_id = pr.pull_request_id
2046 pr_id = pr.pull_request_id
2050 pr_repo = pr.target_repo.repo_name
2047 pr_repo = pr.target_repo.repo_name
2051 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2048 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2052
2049
2053 for stat in status_results:
2050 for stat in status_results:
2054 pr_id = pr_repo = None
2051 pr_id = pr_repo = None
2055 if stat.pull_request:
2052 if stat.pull_request:
2056 pr_id = stat.pull_request.pull_request_id
2053 pr_id = stat.pull_request.pull_request_id
2057 pr_repo = stat.pull_request.target_repo.repo_name
2054 pr_repo = stat.pull_request.target_repo.repo_name
2058 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2055 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2059 pr_id, pr_repo]
2056 pr_id, pr_repo]
2060 return grouped
2057 return grouped
2061
2058
2062 # ==========================================================================
2059 # ==========================================================================
2063 # SCM CACHE INSTANCE
2060 # SCM CACHE INSTANCE
2064 # ==========================================================================
2061 # ==========================================================================
2065
2062
2066 def scm_instance(self, **kwargs):
2063 def scm_instance(self, **kwargs):
2067 import rhodecode
2064 import rhodecode
2068
2065
2069 # Passing a config will not hit the cache currently only used
2066 # Passing a config will not hit the cache currently only used
2070 # for repo2dbmapper
2067 # for repo2dbmapper
2071 config = kwargs.pop('config', None)
2068 config = kwargs.pop('config', None)
2072 cache = kwargs.pop('cache', None)
2069 cache = kwargs.pop('cache', None)
2073 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2070 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2074 # if cache is NOT defined use default global, else we have a full
2071 # if cache is NOT defined use default global, else we have a full
2075 # control over cache behaviour
2072 # control over cache behaviour
2076 if cache is None and full_cache and not config:
2073 if cache is None and full_cache and not config:
2077 return self._get_instance_cached()
2074 return self._get_instance_cached()
2078 return self._get_instance(cache=bool(cache), config=config)
2075 return self._get_instance(cache=bool(cache), config=config)
2079
2076
2080 def _get_instance_cached(self):
2077 def _get_instance_cached(self):
2081 @cache_region('long_term')
2078 @cache_region('long_term')
2082 def _get_repo(cache_key):
2079 def _get_repo(cache_key):
2083 return self._get_instance()
2080 return self._get_instance()
2084
2081
2085 invalidator_context = CacheKey.repo_context_cache(
2082 invalidator_context = CacheKey.repo_context_cache(
2086 _get_repo, self.repo_name, None, thread_scoped=True)
2083 _get_repo, self.repo_name, None, thread_scoped=True)
2087
2084
2088 with invalidator_context as context:
2085 with invalidator_context as context:
2089 context.invalidate()
2086 context.invalidate()
2090 repo = context.compute()
2087 repo = context.compute()
2091
2088
2092 return repo
2089 return repo
2093
2090
2094 def _get_instance(self, cache=True, config=None):
2091 def _get_instance(self, cache=True, config=None):
2095 config = config or self._config
2092 config = config or self._config
2096 custom_wire = {
2093 custom_wire = {
2097 'cache': cache # controls the vcs.remote cache
2094 'cache': cache # controls the vcs.remote cache
2098 }
2095 }
2099 repo = get_vcs_instance(
2096 repo = get_vcs_instance(
2100 repo_path=safe_str(self.repo_full_path),
2097 repo_path=safe_str(self.repo_full_path),
2101 config=config,
2098 config=config,
2102 with_wire=custom_wire,
2099 with_wire=custom_wire,
2103 create=False,
2100 create=False,
2104 _vcs_alias=self.repo_type)
2101 _vcs_alias=self.repo_type)
2105
2102
2106 return repo
2103 return repo
2107
2104
2108 def __json__(self):
2105 def __json__(self):
2109 return {'landing_rev': self.landing_rev}
2106 return {'landing_rev': self.landing_rev}
2110
2107
2111 def get_dict(self):
2108 def get_dict(self):
2112
2109
2113 # Since we transformed `repo_name` to a hybrid property, we need to
2110 # Since we transformed `repo_name` to a hybrid property, we need to
2114 # keep compatibility with the code which uses `repo_name` field.
2111 # keep compatibility with the code which uses `repo_name` field.
2115
2112
2116 result = super(Repository, self).get_dict()
2113 result = super(Repository, self).get_dict()
2117 result['repo_name'] = result.pop('_repo_name', None)
2114 result['repo_name'] = result.pop('_repo_name', None)
2118 return result
2115 return result
2119
2116
2120
2117
2121 class RepoGroup(Base, BaseModel):
2118 class RepoGroup(Base, BaseModel):
2122 __tablename__ = 'groups'
2119 __tablename__ = 'groups'
2123 __table_args__ = (
2120 __table_args__ = (
2124 UniqueConstraint('group_name', 'group_parent_id'),
2121 UniqueConstraint('group_name', 'group_parent_id'),
2125 CheckConstraint('group_id != group_parent_id'),
2122 CheckConstraint('group_id != group_parent_id'),
2126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2123 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2127 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2124 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2128 )
2125 )
2129 __mapper_args__ = {'order_by': 'group_name'}
2126 __mapper_args__ = {'order_by': 'group_name'}
2130
2127
2131 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2128 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2132
2129
2133 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2130 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2134 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2131 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2135 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2132 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2136 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2133 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2137 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2134 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2135 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2139 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2136 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2140 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2137 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2141
2138
2142 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2139 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2143 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2140 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2144 parent_group = relationship('RepoGroup', remote_side=group_id)
2141 parent_group = relationship('RepoGroup', remote_side=group_id)
2145 user = relationship('User')
2142 user = relationship('User')
2146 integrations = relationship('Integration',
2143 integrations = relationship('Integration',
2147 cascade="all, delete, delete-orphan")
2144 cascade="all, delete, delete-orphan")
2148
2145
2149 def __init__(self, group_name='', parent_group=None):
2146 def __init__(self, group_name='', parent_group=None):
2150 self.group_name = group_name
2147 self.group_name = group_name
2151 self.parent_group = parent_group
2148 self.parent_group = parent_group
2152
2149
2153 def __unicode__(self):
2150 def __unicode__(self):
2154 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2151 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
2155 self.group_name)
2152 self.group_name)
2156
2153
2157 @classmethod
2154 @classmethod
2158 def _generate_choice(cls, repo_group):
2155 def _generate_choice(cls, repo_group):
2159 from webhelpers.html import literal as _literal
2156 from webhelpers.html import literal as _literal
2160 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2157 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2161 return repo_group.group_id, _name(repo_group.full_path_splitted)
2158 return repo_group.group_id, _name(repo_group.full_path_splitted)
2162
2159
2163 @classmethod
2160 @classmethod
2164 def groups_choices(cls, groups=None, show_empty_group=True):
2161 def groups_choices(cls, groups=None, show_empty_group=True):
2165 if not groups:
2162 if not groups:
2166 groups = cls.query().all()
2163 groups = cls.query().all()
2167
2164
2168 repo_groups = []
2165 repo_groups = []
2169 if show_empty_group:
2166 if show_empty_group:
2170 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2167 repo_groups = [('-1', u'-- %s --' % _('No parent'))]
2171
2168
2172 repo_groups.extend([cls._generate_choice(x) for x in groups])
2169 repo_groups.extend([cls._generate_choice(x) for x in groups])
2173
2170
2174 repo_groups = sorted(
2171 repo_groups = sorted(
2175 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2172 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2176 return repo_groups
2173 return repo_groups
2177
2174
2178 @classmethod
2175 @classmethod
2179 def url_sep(cls):
2176 def url_sep(cls):
2180 return URL_SEP
2177 return URL_SEP
2181
2178
2182 @classmethod
2179 @classmethod
2183 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2180 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2184 if case_insensitive:
2181 if case_insensitive:
2185 gr = cls.query().filter(func.lower(cls.group_name)
2182 gr = cls.query().filter(func.lower(cls.group_name)
2186 == func.lower(group_name))
2183 == func.lower(group_name))
2187 else:
2184 else:
2188 gr = cls.query().filter(cls.group_name == group_name)
2185 gr = cls.query().filter(cls.group_name == group_name)
2189 if cache:
2186 if cache:
2190 gr = gr.options(FromCache(
2187 gr = gr.options(FromCache(
2191 "sql_cache_short",
2188 "sql_cache_short",
2192 "get_group_%s" % _hash_key(group_name)))
2189 "get_group_%s" % _hash_key(group_name)))
2193 return gr.scalar()
2190 return gr.scalar()
2194
2191
2195 @classmethod
2192 @classmethod
2196 def get_user_personal_repo_group(cls, user_id):
2193 def get_user_personal_repo_group(cls, user_id):
2197 user = User.get(user_id)
2194 user = User.get(user_id)
2198 return cls.query()\
2195 return cls.query()\
2199 .filter(cls.personal == true())\
2196 .filter(cls.personal == true())\
2200 .filter(cls.user == user).scalar()
2197 .filter(cls.user == user).scalar()
2201
2198
2202 @classmethod
2199 @classmethod
2203 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2200 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2204 case_insensitive=True):
2201 case_insensitive=True):
2205 q = RepoGroup.query()
2202 q = RepoGroup.query()
2206
2203
2207 if not isinstance(user_id, Optional):
2204 if not isinstance(user_id, Optional):
2208 q = q.filter(RepoGroup.user_id == user_id)
2205 q = q.filter(RepoGroup.user_id == user_id)
2209
2206
2210 if not isinstance(group_id, Optional):
2207 if not isinstance(group_id, Optional):
2211 q = q.filter(RepoGroup.group_parent_id == group_id)
2208 q = q.filter(RepoGroup.group_parent_id == group_id)
2212
2209
2213 if case_insensitive:
2210 if case_insensitive:
2214 q = q.order_by(func.lower(RepoGroup.group_name))
2211 q = q.order_by(func.lower(RepoGroup.group_name))
2215 else:
2212 else:
2216 q = q.order_by(RepoGroup.group_name)
2213 q = q.order_by(RepoGroup.group_name)
2217 return q.all()
2214 return q.all()
2218
2215
2219 @property
2216 @property
2220 def parents(self):
2217 def parents(self):
2221 parents_recursion_limit = 10
2218 parents_recursion_limit = 10
2222 groups = []
2219 groups = []
2223 if self.parent_group is None:
2220 if self.parent_group is None:
2224 return groups
2221 return groups
2225 cur_gr = self.parent_group
2222 cur_gr = self.parent_group
2226 groups.insert(0, cur_gr)
2223 groups.insert(0, cur_gr)
2227 cnt = 0
2224 cnt = 0
2228 while 1:
2225 while 1:
2229 cnt += 1
2226 cnt += 1
2230 gr = getattr(cur_gr, 'parent_group', None)
2227 gr = getattr(cur_gr, 'parent_group', None)
2231 cur_gr = cur_gr.parent_group
2228 cur_gr = cur_gr.parent_group
2232 if gr is None:
2229 if gr is None:
2233 break
2230 break
2234 if cnt == parents_recursion_limit:
2231 if cnt == parents_recursion_limit:
2235 # this will prevent accidental infinit loops
2232 # this will prevent accidental infinit loops
2236 log.error(('more than %s parents found for group %s, stopping '
2233 log.error(('more than %s parents found for group %s, stopping '
2237 'recursive parent fetching' % (parents_recursion_limit, self)))
2234 'recursive parent fetching' % (parents_recursion_limit, self)))
2238 break
2235 break
2239
2236
2240 groups.insert(0, gr)
2237 groups.insert(0, gr)
2241 return groups
2238 return groups
2242
2239
2243 @property
2240 @property
2244 def children(self):
2241 def children(self):
2245 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2242 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2246
2243
2247 @property
2244 @property
2248 def name(self):
2245 def name(self):
2249 return self.group_name.split(RepoGroup.url_sep())[-1]
2246 return self.group_name.split(RepoGroup.url_sep())[-1]
2250
2247
2251 @property
2248 @property
2252 def full_path(self):
2249 def full_path(self):
2253 return self.group_name
2250 return self.group_name
2254
2251
2255 @property
2252 @property
2256 def full_path_splitted(self):
2253 def full_path_splitted(self):
2257 return self.group_name.split(RepoGroup.url_sep())
2254 return self.group_name.split(RepoGroup.url_sep())
2258
2255
2259 @property
2256 @property
2260 def repositories(self):
2257 def repositories(self):
2261 return Repository.query()\
2258 return Repository.query()\
2262 .filter(Repository.group == self)\
2259 .filter(Repository.group == self)\
2263 .order_by(Repository.repo_name)
2260 .order_by(Repository.repo_name)
2264
2261
2265 @property
2262 @property
2266 def repositories_recursive_count(self):
2263 def repositories_recursive_count(self):
2267 cnt = self.repositories.count()
2264 cnt = self.repositories.count()
2268
2265
2269 def children_count(group):
2266 def children_count(group):
2270 cnt = 0
2267 cnt = 0
2271 for child in group.children:
2268 for child in group.children:
2272 cnt += child.repositories.count()
2269 cnt += child.repositories.count()
2273 cnt += children_count(child)
2270 cnt += children_count(child)
2274 return cnt
2271 return cnt
2275
2272
2276 return cnt + children_count(self)
2273 return cnt + children_count(self)
2277
2274
2278 def _recursive_objects(self, include_repos=True):
2275 def _recursive_objects(self, include_repos=True):
2279 all_ = []
2276 all_ = []
2280
2277
2281 def _get_members(root_gr):
2278 def _get_members(root_gr):
2282 if include_repos:
2279 if include_repos:
2283 for r in root_gr.repositories:
2280 for r in root_gr.repositories:
2284 all_.append(r)
2281 all_.append(r)
2285 childs = root_gr.children.all()
2282 childs = root_gr.children.all()
2286 if childs:
2283 if childs:
2287 for gr in childs:
2284 for gr in childs:
2288 all_.append(gr)
2285 all_.append(gr)
2289 _get_members(gr)
2286 _get_members(gr)
2290
2287
2291 _get_members(self)
2288 _get_members(self)
2292 return [self] + all_
2289 return [self] + all_
2293
2290
2294 def recursive_groups_and_repos(self):
2291 def recursive_groups_and_repos(self):
2295 """
2292 """
2296 Recursive return all groups, with repositories in those groups
2293 Recursive return all groups, with repositories in those groups
2297 """
2294 """
2298 return self._recursive_objects()
2295 return self._recursive_objects()
2299
2296
2300 def recursive_groups(self):
2297 def recursive_groups(self):
2301 """
2298 """
2302 Returns all children groups for this group including children of children
2299 Returns all children groups for this group including children of children
2303 """
2300 """
2304 return self._recursive_objects(include_repos=False)
2301 return self._recursive_objects(include_repos=False)
2305
2302
2306 def get_new_name(self, group_name):
2303 def get_new_name(self, group_name):
2307 """
2304 """
2308 returns new full group name based on parent and new name
2305 returns new full group name based on parent and new name
2309
2306
2310 :param group_name:
2307 :param group_name:
2311 """
2308 """
2312 path_prefix = (self.parent_group.full_path_splitted if
2309 path_prefix = (self.parent_group.full_path_splitted if
2313 self.parent_group else [])
2310 self.parent_group else [])
2314 return RepoGroup.url_sep().join(path_prefix + [group_name])
2311 return RepoGroup.url_sep().join(path_prefix + [group_name])
2315
2312
2316 def permissions(self, with_admins=True, with_owner=True):
2313 def permissions(self, with_admins=True, with_owner=True):
2317 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2314 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2318 q = q.options(joinedload(UserRepoGroupToPerm.group),
2315 q = q.options(joinedload(UserRepoGroupToPerm.group),
2319 joinedload(UserRepoGroupToPerm.user),
2316 joinedload(UserRepoGroupToPerm.user),
2320 joinedload(UserRepoGroupToPerm.permission),)
2317 joinedload(UserRepoGroupToPerm.permission),)
2321
2318
2322 # get owners and admins and permissions. We do a trick of re-writing
2319 # get owners and admins and permissions. We do a trick of re-writing
2323 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2320 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2324 # has a global reference and changing one object propagates to all
2321 # has a global reference and changing one object propagates to all
2325 # others. This means if admin is also an owner admin_row that change
2322 # others. This means if admin is also an owner admin_row that change
2326 # would propagate to both objects
2323 # would propagate to both objects
2327 perm_rows = []
2324 perm_rows = []
2328 for _usr in q.all():
2325 for _usr in q.all():
2329 usr = AttributeDict(_usr.user.get_dict())
2326 usr = AttributeDict(_usr.user.get_dict())
2330 usr.permission = _usr.permission.permission_name
2327 usr.permission = _usr.permission.permission_name
2331 perm_rows.append(usr)
2328 perm_rows.append(usr)
2332
2329
2333 # filter the perm rows by 'default' first and then sort them by
2330 # filter the perm rows by 'default' first and then sort them by
2334 # admin,write,read,none permissions sorted again alphabetically in
2331 # admin,write,read,none permissions sorted again alphabetically in
2335 # each group
2332 # each group
2336 perm_rows = sorted(perm_rows, key=display_sort)
2333 perm_rows = sorted(perm_rows, key=display_sort)
2337
2334
2338 _admin_perm = 'group.admin'
2335 _admin_perm = 'group.admin'
2339 owner_row = []
2336 owner_row = []
2340 if with_owner:
2337 if with_owner:
2341 usr = AttributeDict(self.user.get_dict())
2338 usr = AttributeDict(self.user.get_dict())
2342 usr.owner_row = True
2339 usr.owner_row = True
2343 usr.permission = _admin_perm
2340 usr.permission = _admin_perm
2344 owner_row.append(usr)
2341 owner_row.append(usr)
2345
2342
2346 super_admin_rows = []
2343 super_admin_rows = []
2347 if with_admins:
2344 if with_admins:
2348 for usr in User.get_all_super_admins():
2345 for usr in User.get_all_super_admins():
2349 # if this admin is also owner, don't double the record
2346 # if this admin is also owner, don't double the record
2350 if usr.user_id == owner_row[0].user_id:
2347 if usr.user_id == owner_row[0].user_id:
2351 owner_row[0].admin_row = True
2348 owner_row[0].admin_row = True
2352 else:
2349 else:
2353 usr = AttributeDict(usr.get_dict())
2350 usr = AttributeDict(usr.get_dict())
2354 usr.admin_row = True
2351 usr.admin_row = True
2355 usr.permission = _admin_perm
2352 usr.permission = _admin_perm
2356 super_admin_rows.append(usr)
2353 super_admin_rows.append(usr)
2357
2354
2358 return super_admin_rows + owner_row + perm_rows
2355 return super_admin_rows + owner_row + perm_rows
2359
2356
2360 def permission_user_groups(self):
2357 def permission_user_groups(self):
2361 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2358 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2362 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2359 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2363 joinedload(UserGroupRepoGroupToPerm.users_group),
2360 joinedload(UserGroupRepoGroupToPerm.users_group),
2364 joinedload(UserGroupRepoGroupToPerm.permission),)
2361 joinedload(UserGroupRepoGroupToPerm.permission),)
2365
2362
2366 perm_rows = []
2363 perm_rows = []
2367 for _user_group in q.all():
2364 for _user_group in q.all():
2368 usr = AttributeDict(_user_group.users_group.get_dict())
2365 usr = AttributeDict(_user_group.users_group.get_dict())
2369 usr.permission = _user_group.permission.permission_name
2366 usr.permission = _user_group.permission.permission_name
2370 perm_rows.append(usr)
2367 perm_rows.append(usr)
2371
2368
2372 return perm_rows
2369 return perm_rows
2373
2370
2374 def get_api_data(self):
2371 def get_api_data(self):
2375 """
2372 """
2376 Common function for generating api data
2373 Common function for generating api data
2377
2374
2378 """
2375 """
2379 group = self
2376 group = self
2380 data = {
2377 data = {
2381 'group_id': group.group_id,
2378 'group_id': group.group_id,
2382 'group_name': group.group_name,
2379 'group_name': group.group_name,
2383 'group_description': group.group_description,
2380 'group_description': group.group_description,
2384 'parent_group': group.parent_group.group_name if group.parent_group else None,
2381 'parent_group': group.parent_group.group_name if group.parent_group else None,
2385 'repositories': [x.repo_name for x in group.repositories],
2382 'repositories': [x.repo_name for x in group.repositories],
2386 'owner': group.user.username,
2383 'owner': group.user.username,
2387 }
2384 }
2388 return data
2385 return data
2389
2386
2390
2387
2391 class Permission(Base, BaseModel):
2388 class Permission(Base, BaseModel):
2392 __tablename__ = 'permissions'
2389 __tablename__ = 'permissions'
2393 __table_args__ = (
2390 __table_args__ = (
2394 Index('p_perm_name_idx', 'permission_name'),
2391 Index('p_perm_name_idx', 'permission_name'),
2395 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2392 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2396 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2393 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2397 )
2394 )
2398 PERMS = [
2395 PERMS = [
2399 ('hg.admin', _('RhodeCode Super Administrator')),
2396 ('hg.admin', _('RhodeCode Super Administrator')),
2400
2397
2401 ('repository.none', _('Repository no access')),
2398 ('repository.none', _('Repository no access')),
2402 ('repository.read', _('Repository read access')),
2399 ('repository.read', _('Repository read access')),
2403 ('repository.write', _('Repository write access')),
2400 ('repository.write', _('Repository write access')),
2404 ('repository.admin', _('Repository admin access')),
2401 ('repository.admin', _('Repository admin access')),
2405
2402
2406 ('group.none', _('Repository group no access')),
2403 ('group.none', _('Repository group no access')),
2407 ('group.read', _('Repository group read access')),
2404 ('group.read', _('Repository group read access')),
2408 ('group.write', _('Repository group write access')),
2405 ('group.write', _('Repository group write access')),
2409 ('group.admin', _('Repository group admin access')),
2406 ('group.admin', _('Repository group admin access')),
2410
2407
2411 ('usergroup.none', _('User group no access')),
2408 ('usergroup.none', _('User group no access')),
2412 ('usergroup.read', _('User group read access')),
2409 ('usergroup.read', _('User group read access')),
2413 ('usergroup.write', _('User group write access')),
2410 ('usergroup.write', _('User group write access')),
2414 ('usergroup.admin', _('User group admin access')),
2411 ('usergroup.admin', _('User group admin access')),
2415
2412
2416 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2413 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2417 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2414 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2418
2415
2419 ('hg.usergroup.create.false', _('User Group creation disabled')),
2416 ('hg.usergroup.create.false', _('User Group creation disabled')),
2420 ('hg.usergroup.create.true', _('User Group creation enabled')),
2417 ('hg.usergroup.create.true', _('User Group creation enabled')),
2421
2418
2422 ('hg.create.none', _('Repository creation disabled')),
2419 ('hg.create.none', _('Repository creation disabled')),
2423 ('hg.create.repository', _('Repository creation enabled')),
2420 ('hg.create.repository', _('Repository creation enabled')),
2424 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2421 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2425 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2422 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2426
2423
2427 ('hg.fork.none', _('Repository forking disabled')),
2424 ('hg.fork.none', _('Repository forking disabled')),
2428 ('hg.fork.repository', _('Repository forking enabled')),
2425 ('hg.fork.repository', _('Repository forking enabled')),
2429
2426
2430 ('hg.register.none', _('Registration disabled')),
2427 ('hg.register.none', _('Registration disabled')),
2431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2428 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2429 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2433
2430
2434 ('hg.password_reset.enabled', _('Password reset enabled')),
2431 ('hg.password_reset.enabled', _('Password reset enabled')),
2435 ('hg.password_reset.hidden', _('Password reset hidden')),
2432 ('hg.password_reset.hidden', _('Password reset hidden')),
2436 ('hg.password_reset.disabled', _('Password reset disabled')),
2433 ('hg.password_reset.disabled', _('Password reset disabled')),
2437
2434
2438 ('hg.extern_activate.manual', _('Manual activation of external account')),
2435 ('hg.extern_activate.manual', _('Manual activation of external account')),
2439 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2436 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2440
2437
2441 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2438 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2442 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2439 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2443 ]
2440 ]
2444
2441
2445 # definition of system default permissions for DEFAULT user
2442 # definition of system default permissions for DEFAULT user
2446 DEFAULT_USER_PERMISSIONS = [
2443 DEFAULT_USER_PERMISSIONS = [
2447 'repository.read',
2444 'repository.read',
2448 'group.read',
2445 'group.read',
2449 'usergroup.read',
2446 'usergroup.read',
2450 'hg.create.repository',
2447 'hg.create.repository',
2451 'hg.repogroup.create.false',
2448 'hg.repogroup.create.false',
2452 'hg.usergroup.create.false',
2449 'hg.usergroup.create.false',
2453 'hg.create.write_on_repogroup.true',
2450 'hg.create.write_on_repogroup.true',
2454 'hg.fork.repository',
2451 'hg.fork.repository',
2455 'hg.register.manual_activate',
2452 'hg.register.manual_activate',
2456 'hg.password_reset.enabled',
2453 'hg.password_reset.enabled',
2457 'hg.extern_activate.auto',
2454 'hg.extern_activate.auto',
2458 'hg.inherit_default_perms.true',
2455 'hg.inherit_default_perms.true',
2459 ]
2456 ]
2460
2457
2461 # defines which permissions are more important higher the more important
2458 # defines which permissions are more important higher the more important
2462 # Weight defines which permissions are more important.
2459 # Weight defines which permissions are more important.
2463 # The higher number the more important.
2460 # The higher number the more important.
2464 PERM_WEIGHTS = {
2461 PERM_WEIGHTS = {
2465 'repository.none': 0,
2462 'repository.none': 0,
2466 'repository.read': 1,
2463 'repository.read': 1,
2467 'repository.write': 3,
2464 'repository.write': 3,
2468 'repository.admin': 4,
2465 'repository.admin': 4,
2469
2466
2470 'group.none': 0,
2467 'group.none': 0,
2471 'group.read': 1,
2468 'group.read': 1,
2472 'group.write': 3,
2469 'group.write': 3,
2473 'group.admin': 4,
2470 'group.admin': 4,
2474
2471
2475 'usergroup.none': 0,
2472 'usergroup.none': 0,
2476 'usergroup.read': 1,
2473 'usergroup.read': 1,
2477 'usergroup.write': 3,
2474 'usergroup.write': 3,
2478 'usergroup.admin': 4,
2475 'usergroup.admin': 4,
2479
2476
2480 'hg.repogroup.create.false': 0,
2477 'hg.repogroup.create.false': 0,
2481 'hg.repogroup.create.true': 1,
2478 'hg.repogroup.create.true': 1,
2482
2479
2483 'hg.usergroup.create.false': 0,
2480 'hg.usergroup.create.false': 0,
2484 'hg.usergroup.create.true': 1,
2481 'hg.usergroup.create.true': 1,
2485
2482
2486 'hg.fork.none': 0,
2483 'hg.fork.none': 0,
2487 'hg.fork.repository': 1,
2484 'hg.fork.repository': 1,
2488 'hg.create.none': 0,
2485 'hg.create.none': 0,
2489 'hg.create.repository': 1
2486 'hg.create.repository': 1
2490 }
2487 }
2491
2488
2492 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2489 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2493 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2490 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2494 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2491 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2495
2492
2496 def __unicode__(self):
2493 def __unicode__(self):
2497 return u"<%s('%s:%s')>" % (
2494 return u"<%s('%s:%s')>" % (
2498 self.__class__.__name__, self.permission_id, self.permission_name
2495 self.__class__.__name__, self.permission_id, self.permission_name
2499 )
2496 )
2500
2497
2501 @classmethod
2498 @classmethod
2502 def get_by_key(cls, key):
2499 def get_by_key(cls, key):
2503 return cls.query().filter(cls.permission_name == key).scalar()
2500 return cls.query().filter(cls.permission_name == key).scalar()
2504
2501
2505 @classmethod
2502 @classmethod
2506 def get_default_repo_perms(cls, user_id, repo_id=None):
2503 def get_default_repo_perms(cls, user_id, repo_id=None):
2507 q = Session().query(UserRepoToPerm, Repository, Permission)\
2504 q = Session().query(UserRepoToPerm, Repository, Permission)\
2508 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2505 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2509 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2506 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2510 .filter(UserRepoToPerm.user_id == user_id)
2507 .filter(UserRepoToPerm.user_id == user_id)
2511 if repo_id:
2508 if repo_id:
2512 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2509 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2513 return q.all()
2510 return q.all()
2514
2511
2515 @classmethod
2512 @classmethod
2516 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2513 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2517 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2514 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2518 .join(
2515 .join(
2519 Permission,
2516 Permission,
2520 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2517 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2521 .join(
2518 .join(
2522 Repository,
2519 Repository,
2523 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2520 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2524 .join(
2521 .join(
2525 UserGroup,
2522 UserGroup,
2526 UserGroupRepoToPerm.users_group_id ==
2523 UserGroupRepoToPerm.users_group_id ==
2527 UserGroup.users_group_id)\
2524 UserGroup.users_group_id)\
2528 .join(
2525 .join(
2529 UserGroupMember,
2526 UserGroupMember,
2530 UserGroupRepoToPerm.users_group_id ==
2527 UserGroupRepoToPerm.users_group_id ==
2531 UserGroupMember.users_group_id)\
2528 UserGroupMember.users_group_id)\
2532 .filter(
2529 .filter(
2533 UserGroupMember.user_id == user_id,
2530 UserGroupMember.user_id == user_id,
2534 UserGroup.users_group_active == true())
2531 UserGroup.users_group_active == true())
2535 if repo_id:
2532 if repo_id:
2536 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2533 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2537 return q.all()
2534 return q.all()
2538
2535
2539 @classmethod
2536 @classmethod
2540 def get_default_group_perms(cls, user_id, repo_group_id=None):
2537 def get_default_group_perms(cls, user_id, repo_group_id=None):
2541 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2538 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2542 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2539 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2543 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2540 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2544 .filter(UserRepoGroupToPerm.user_id == user_id)
2541 .filter(UserRepoGroupToPerm.user_id == user_id)
2545 if repo_group_id:
2542 if repo_group_id:
2546 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2543 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2547 return q.all()
2544 return q.all()
2548
2545
2549 @classmethod
2546 @classmethod
2550 def get_default_group_perms_from_user_group(
2547 def get_default_group_perms_from_user_group(
2551 cls, user_id, repo_group_id=None):
2548 cls, user_id, repo_group_id=None):
2552 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2549 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2553 .join(
2550 .join(
2554 Permission,
2551 Permission,
2555 UserGroupRepoGroupToPerm.permission_id ==
2552 UserGroupRepoGroupToPerm.permission_id ==
2556 Permission.permission_id)\
2553 Permission.permission_id)\
2557 .join(
2554 .join(
2558 RepoGroup,
2555 RepoGroup,
2559 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2556 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2560 .join(
2557 .join(
2561 UserGroup,
2558 UserGroup,
2562 UserGroupRepoGroupToPerm.users_group_id ==
2559 UserGroupRepoGroupToPerm.users_group_id ==
2563 UserGroup.users_group_id)\
2560 UserGroup.users_group_id)\
2564 .join(
2561 .join(
2565 UserGroupMember,
2562 UserGroupMember,
2566 UserGroupRepoGroupToPerm.users_group_id ==
2563 UserGroupRepoGroupToPerm.users_group_id ==
2567 UserGroupMember.users_group_id)\
2564 UserGroupMember.users_group_id)\
2568 .filter(
2565 .filter(
2569 UserGroupMember.user_id == user_id,
2566 UserGroupMember.user_id == user_id,
2570 UserGroup.users_group_active == true())
2567 UserGroup.users_group_active == true())
2571 if repo_group_id:
2568 if repo_group_id:
2572 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2569 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2573 return q.all()
2570 return q.all()
2574
2571
2575 @classmethod
2572 @classmethod
2576 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2573 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2577 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2574 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2578 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2575 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2579 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2576 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2580 .filter(UserUserGroupToPerm.user_id == user_id)
2577 .filter(UserUserGroupToPerm.user_id == user_id)
2581 if user_group_id:
2578 if user_group_id:
2582 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2579 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2583 return q.all()
2580 return q.all()
2584
2581
2585 @classmethod
2582 @classmethod
2586 def get_default_user_group_perms_from_user_group(
2583 def get_default_user_group_perms_from_user_group(
2587 cls, user_id, user_group_id=None):
2584 cls, user_id, user_group_id=None):
2588 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2585 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2589 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2586 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2590 .join(
2587 .join(
2591 Permission,
2588 Permission,
2592 UserGroupUserGroupToPerm.permission_id ==
2589 UserGroupUserGroupToPerm.permission_id ==
2593 Permission.permission_id)\
2590 Permission.permission_id)\
2594 .join(
2591 .join(
2595 TargetUserGroup,
2592 TargetUserGroup,
2596 UserGroupUserGroupToPerm.target_user_group_id ==
2593 UserGroupUserGroupToPerm.target_user_group_id ==
2597 TargetUserGroup.users_group_id)\
2594 TargetUserGroup.users_group_id)\
2598 .join(
2595 .join(
2599 UserGroup,
2596 UserGroup,
2600 UserGroupUserGroupToPerm.user_group_id ==
2597 UserGroupUserGroupToPerm.user_group_id ==
2601 UserGroup.users_group_id)\
2598 UserGroup.users_group_id)\
2602 .join(
2599 .join(
2603 UserGroupMember,
2600 UserGroupMember,
2604 UserGroupUserGroupToPerm.user_group_id ==
2601 UserGroupUserGroupToPerm.user_group_id ==
2605 UserGroupMember.users_group_id)\
2602 UserGroupMember.users_group_id)\
2606 .filter(
2603 .filter(
2607 UserGroupMember.user_id == user_id,
2604 UserGroupMember.user_id == user_id,
2608 UserGroup.users_group_active == true())
2605 UserGroup.users_group_active == true())
2609 if user_group_id:
2606 if user_group_id:
2610 q = q.filter(
2607 q = q.filter(
2611 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2608 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2612
2609
2613 return q.all()
2610 return q.all()
2614
2611
2615
2612
2616 class UserRepoToPerm(Base, BaseModel):
2613 class UserRepoToPerm(Base, BaseModel):
2617 __tablename__ = 'repo_to_perm'
2614 __tablename__ = 'repo_to_perm'
2618 __table_args__ = (
2615 __table_args__ = (
2619 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2616 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2617 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2618 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2622 )
2619 )
2623 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2620 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2624 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2621 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2625 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2622 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2626 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2623 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2627
2624
2628 user = relationship('User')
2625 user = relationship('User')
2629 repository = relationship('Repository')
2626 repository = relationship('Repository')
2630 permission = relationship('Permission')
2627 permission = relationship('Permission')
2631
2628
2632 @classmethod
2629 @classmethod
2633 def create(cls, user, repository, permission):
2630 def create(cls, user, repository, permission):
2634 n = cls()
2631 n = cls()
2635 n.user = user
2632 n.user = user
2636 n.repository = repository
2633 n.repository = repository
2637 n.permission = permission
2634 n.permission = permission
2638 Session().add(n)
2635 Session().add(n)
2639 return n
2636 return n
2640
2637
2641 def __unicode__(self):
2638 def __unicode__(self):
2642 return u'<%s => %s >' % (self.user, self.repository)
2639 return u'<%s => %s >' % (self.user, self.repository)
2643
2640
2644
2641
2645 class UserUserGroupToPerm(Base, BaseModel):
2642 class UserUserGroupToPerm(Base, BaseModel):
2646 __tablename__ = 'user_user_group_to_perm'
2643 __tablename__ = 'user_user_group_to_perm'
2647 __table_args__ = (
2644 __table_args__ = (
2648 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2645 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2649 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2646 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2650 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2647 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2651 )
2648 )
2652 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2649 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2653 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2650 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2654 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2651 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2655 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2652 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2656
2653
2657 user = relationship('User')
2654 user = relationship('User')
2658 user_group = relationship('UserGroup')
2655 user_group = relationship('UserGroup')
2659 permission = relationship('Permission')
2656 permission = relationship('Permission')
2660
2657
2661 @classmethod
2658 @classmethod
2662 def create(cls, user, user_group, permission):
2659 def create(cls, user, user_group, permission):
2663 n = cls()
2660 n = cls()
2664 n.user = user
2661 n.user = user
2665 n.user_group = user_group
2662 n.user_group = user_group
2666 n.permission = permission
2663 n.permission = permission
2667 Session().add(n)
2664 Session().add(n)
2668 return n
2665 return n
2669
2666
2670 def __unicode__(self):
2667 def __unicode__(self):
2671 return u'<%s => %s >' % (self.user, self.user_group)
2668 return u'<%s => %s >' % (self.user, self.user_group)
2672
2669
2673
2670
2674 class UserToPerm(Base, BaseModel):
2671 class UserToPerm(Base, BaseModel):
2675 __tablename__ = 'user_to_perm'
2672 __tablename__ = 'user_to_perm'
2676 __table_args__ = (
2673 __table_args__ = (
2677 UniqueConstraint('user_id', 'permission_id'),
2674 UniqueConstraint('user_id', 'permission_id'),
2678 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2679 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2676 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2680 )
2677 )
2681 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2678 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2682 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2683 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2680 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2684
2681
2685 user = relationship('User')
2682 user = relationship('User')
2686 permission = relationship('Permission', lazy='joined')
2683 permission = relationship('Permission', lazy='joined')
2687
2684
2688 def __unicode__(self):
2685 def __unicode__(self):
2689 return u'<%s => %s >' % (self.user, self.permission)
2686 return u'<%s => %s >' % (self.user, self.permission)
2690
2687
2691
2688
2692 class UserGroupRepoToPerm(Base, BaseModel):
2689 class UserGroupRepoToPerm(Base, BaseModel):
2693 __tablename__ = 'users_group_repo_to_perm'
2690 __tablename__ = 'users_group_repo_to_perm'
2694 __table_args__ = (
2691 __table_args__ = (
2695 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2692 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2696 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2697 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2694 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2698 )
2695 )
2699 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2696 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2700 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2697 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2698 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2702 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2699 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2703
2700
2704 users_group = relationship('UserGroup')
2701 users_group = relationship('UserGroup')
2705 permission = relationship('Permission')
2702 permission = relationship('Permission')
2706 repository = relationship('Repository')
2703 repository = relationship('Repository')
2707
2704
2708 @classmethod
2705 @classmethod
2709 def create(cls, users_group, repository, permission):
2706 def create(cls, users_group, repository, permission):
2710 n = cls()
2707 n = cls()
2711 n.users_group = users_group
2708 n.users_group = users_group
2712 n.repository = repository
2709 n.repository = repository
2713 n.permission = permission
2710 n.permission = permission
2714 Session().add(n)
2711 Session().add(n)
2715 return n
2712 return n
2716
2713
2717 def __unicode__(self):
2714 def __unicode__(self):
2718 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2715 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2719
2716
2720
2717
2721 class UserGroupUserGroupToPerm(Base, BaseModel):
2718 class UserGroupUserGroupToPerm(Base, BaseModel):
2722 __tablename__ = 'user_group_user_group_to_perm'
2719 __tablename__ = 'user_group_user_group_to_perm'
2723 __table_args__ = (
2720 __table_args__ = (
2724 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2721 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2725 CheckConstraint('target_user_group_id != user_group_id'),
2722 CheckConstraint('target_user_group_id != user_group_id'),
2726 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2727 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2724 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2728 )
2725 )
2729 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)
2726 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)
2730 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2727 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2731 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2728 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2732 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2729 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2733
2730
2734 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2731 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2735 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2732 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2736 permission = relationship('Permission')
2733 permission = relationship('Permission')
2737
2734
2738 @classmethod
2735 @classmethod
2739 def create(cls, target_user_group, user_group, permission):
2736 def create(cls, target_user_group, user_group, permission):
2740 n = cls()
2737 n = cls()
2741 n.target_user_group = target_user_group
2738 n.target_user_group = target_user_group
2742 n.user_group = user_group
2739 n.user_group = user_group
2743 n.permission = permission
2740 n.permission = permission
2744 Session().add(n)
2741 Session().add(n)
2745 return n
2742 return n
2746
2743
2747 def __unicode__(self):
2744 def __unicode__(self):
2748 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2745 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2749
2746
2750
2747
2751 class UserGroupToPerm(Base, BaseModel):
2748 class UserGroupToPerm(Base, BaseModel):
2752 __tablename__ = 'users_group_to_perm'
2749 __tablename__ = 'users_group_to_perm'
2753 __table_args__ = (
2750 __table_args__ = (
2754 UniqueConstraint('users_group_id', 'permission_id',),
2751 UniqueConstraint('users_group_id', 'permission_id',),
2755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2752 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2756 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2753 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2757 )
2754 )
2758 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2755 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2759 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2756 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2760 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2757 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2761
2758
2762 users_group = relationship('UserGroup')
2759 users_group = relationship('UserGroup')
2763 permission = relationship('Permission')
2760 permission = relationship('Permission')
2764
2761
2765
2762
2766 class UserRepoGroupToPerm(Base, BaseModel):
2763 class UserRepoGroupToPerm(Base, BaseModel):
2767 __tablename__ = 'user_repo_group_to_perm'
2764 __tablename__ = 'user_repo_group_to_perm'
2768 __table_args__ = (
2765 __table_args__ = (
2769 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2766 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2767 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2771 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2768 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2772 )
2769 )
2773
2770
2774 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2771 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2775 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2772 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2776 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2773 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2774 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2778
2775
2779 user = relationship('User')
2776 user = relationship('User')
2780 group = relationship('RepoGroup')
2777 group = relationship('RepoGroup')
2781 permission = relationship('Permission')
2778 permission = relationship('Permission')
2782
2779
2783 @classmethod
2780 @classmethod
2784 def create(cls, user, repository_group, permission):
2781 def create(cls, user, repository_group, permission):
2785 n = cls()
2782 n = cls()
2786 n.user = user
2783 n.user = user
2787 n.group = repository_group
2784 n.group = repository_group
2788 n.permission = permission
2785 n.permission = permission
2789 Session().add(n)
2786 Session().add(n)
2790 return n
2787 return n
2791
2788
2792
2789
2793 class UserGroupRepoGroupToPerm(Base, BaseModel):
2790 class UserGroupRepoGroupToPerm(Base, BaseModel):
2794 __tablename__ = 'users_group_repo_group_to_perm'
2791 __tablename__ = 'users_group_repo_group_to_perm'
2795 __table_args__ = (
2792 __table_args__ = (
2796 UniqueConstraint('users_group_id', 'group_id'),
2793 UniqueConstraint('users_group_id', 'group_id'),
2797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2794 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2798 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2795 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2799 )
2796 )
2800
2797
2801 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)
2798 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)
2802 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2799 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2803 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2800 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2804 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2801 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2805
2802
2806 users_group = relationship('UserGroup')
2803 users_group = relationship('UserGroup')
2807 permission = relationship('Permission')
2804 permission = relationship('Permission')
2808 group = relationship('RepoGroup')
2805 group = relationship('RepoGroup')
2809
2806
2810 @classmethod
2807 @classmethod
2811 def create(cls, user_group, repository_group, permission):
2808 def create(cls, user_group, repository_group, permission):
2812 n = cls()
2809 n = cls()
2813 n.users_group = user_group
2810 n.users_group = user_group
2814 n.group = repository_group
2811 n.group = repository_group
2815 n.permission = permission
2812 n.permission = permission
2816 Session().add(n)
2813 Session().add(n)
2817 return n
2814 return n
2818
2815
2819 def __unicode__(self):
2816 def __unicode__(self):
2820 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2817 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2821
2818
2822
2819
2823 class Statistics(Base, BaseModel):
2820 class Statistics(Base, BaseModel):
2824 __tablename__ = 'statistics'
2821 __tablename__ = 'statistics'
2825 __table_args__ = (
2822 __table_args__ = (
2826 UniqueConstraint('repository_id'),
2823 UniqueConstraint('repository_id'),
2827 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2824 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2828 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2825 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2829 )
2826 )
2830 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2827 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2831 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2828 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2832 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2829 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2833 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2830 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2834 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2831 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2835 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2832 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2836
2833
2837 repository = relationship('Repository', single_parent=True)
2834 repository = relationship('Repository', single_parent=True)
2838
2835
2839
2836
2840 class UserFollowing(Base, BaseModel):
2837 class UserFollowing(Base, BaseModel):
2841 __tablename__ = 'user_followings'
2838 __tablename__ = 'user_followings'
2842 __table_args__ = (
2839 __table_args__ = (
2843 UniqueConstraint('user_id', 'follows_repository_id'),
2840 UniqueConstraint('user_id', 'follows_repository_id'),
2844 UniqueConstraint('user_id', 'follows_user_id'),
2841 UniqueConstraint('user_id', 'follows_user_id'),
2845 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2842 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2846 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2843 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2847 )
2844 )
2848
2845
2849 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2846 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2850 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2851 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2848 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
2852 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2849 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
2853 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2850 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2854
2851
2855 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2852 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
2856
2853
2857 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2854 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
2858 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2855 follows_repository = relationship('Repository', order_by='Repository.repo_name')
2859
2856
2860 @classmethod
2857 @classmethod
2861 def get_repo_followers(cls, repo_id):
2858 def get_repo_followers(cls, repo_id):
2862 return cls.query().filter(cls.follows_repo_id == repo_id)
2859 return cls.query().filter(cls.follows_repo_id == repo_id)
2863
2860
2864
2861
2865 class CacheKey(Base, BaseModel):
2862 class CacheKey(Base, BaseModel):
2866 __tablename__ = 'cache_invalidation'
2863 __tablename__ = 'cache_invalidation'
2867 __table_args__ = (
2864 __table_args__ = (
2868 UniqueConstraint('cache_key'),
2865 UniqueConstraint('cache_key'),
2869 Index('key_idx', 'cache_key'),
2866 Index('key_idx', 'cache_key'),
2870 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2871 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2868 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2872 )
2869 )
2873 CACHE_TYPE_ATOM = 'ATOM'
2870 CACHE_TYPE_ATOM = 'ATOM'
2874 CACHE_TYPE_RSS = 'RSS'
2871 CACHE_TYPE_RSS = 'RSS'
2875 CACHE_TYPE_README = 'README'
2872 CACHE_TYPE_README = 'README'
2876
2873
2877 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2874 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2878 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2875 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
2879 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2876 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
2880 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2877 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
2881
2878
2882 def __init__(self, cache_key, cache_args=''):
2879 def __init__(self, cache_key, cache_args=''):
2883 self.cache_key = cache_key
2880 self.cache_key = cache_key
2884 self.cache_args = cache_args
2881 self.cache_args = cache_args
2885 self.cache_active = False
2882 self.cache_active = False
2886
2883
2887 def __unicode__(self):
2884 def __unicode__(self):
2888 return u"<%s('%s:%s[%s]')>" % (
2885 return u"<%s('%s:%s[%s]')>" % (
2889 self.__class__.__name__,
2886 self.__class__.__name__,
2890 self.cache_id, self.cache_key, self.cache_active)
2887 self.cache_id, self.cache_key, self.cache_active)
2891
2888
2892 def _cache_key_partition(self):
2889 def _cache_key_partition(self):
2893 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2890 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
2894 return prefix, repo_name, suffix
2891 return prefix, repo_name, suffix
2895
2892
2896 def get_prefix(self):
2893 def get_prefix(self):
2897 """
2894 """
2898 Try to extract prefix from existing cache key. The key could consist
2895 Try to extract prefix from existing cache key. The key could consist
2899 of prefix, repo_name, suffix
2896 of prefix, repo_name, suffix
2900 """
2897 """
2901 # this returns prefix, repo_name, suffix
2898 # this returns prefix, repo_name, suffix
2902 return self._cache_key_partition()[0]
2899 return self._cache_key_partition()[0]
2903
2900
2904 def get_suffix(self):
2901 def get_suffix(self):
2905 """
2902 """
2906 get suffix that might have been used in _get_cache_key to
2903 get suffix that might have been used in _get_cache_key to
2907 generate self.cache_key. Only used for informational purposes
2904 generate self.cache_key. Only used for informational purposes
2908 in repo_edit.mako.
2905 in repo_edit.mako.
2909 """
2906 """
2910 # prefix, repo_name, suffix
2907 # prefix, repo_name, suffix
2911 return self._cache_key_partition()[2]
2908 return self._cache_key_partition()[2]
2912
2909
2913 @classmethod
2910 @classmethod
2914 def delete_all_cache(cls):
2911 def delete_all_cache(cls):
2915 """
2912 """
2916 Delete all cache keys from database.
2913 Delete all cache keys from database.
2917 Should only be run when all instances are down and all entries
2914 Should only be run when all instances are down and all entries
2918 thus stale.
2915 thus stale.
2919 """
2916 """
2920 cls.query().delete()
2917 cls.query().delete()
2921 Session().commit()
2918 Session().commit()
2922
2919
2923 @classmethod
2920 @classmethod
2924 def get_cache_key(cls, repo_name, cache_type):
2921 def get_cache_key(cls, repo_name, cache_type):
2925 """
2922 """
2926
2923
2927 Generate a cache key for this process of RhodeCode instance.
2924 Generate a cache key for this process of RhodeCode instance.
2928 Prefix most likely will be process id or maybe explicitly set
2925 Prefix most likely will be process id or maybe explicitly set
2929 instance_id from .ini file.
2926 instance_id from .ini file.
2930 """
2927 """
2931 import rhodecode
2928 import rhodecode
2932 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2929 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
2933
2930
2934 repo_as_unicode = safe_unicode(repo_name)
2931 repo_as_unicode = safe_unicode(repo_name)
2935 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2932 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
2936 if cache_type else repo_as_unicode
2933 if cache_type else repo_as_unicode
2937
2934
2938 return u'{}{}'.format(prefix, key)
2935 return u'{}{}'.format(prefix, key)
2939
2936
2940 @classmethod
2937 @classmethod
2941 def set_invalidate(cls, repo_name, delete=False):
2938 def set_invalidate(cls, repo_name, delete=False):
2942 """
2939 """
2943 Mark all caches of a repo as invalid in the database.
2940 Mark all caches of a repo as invalid in the database.
2944 """
2941 """
2945
2942
2946 try:
2943 try:
2947 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2944 qry = Session().query(cls).filter(cls.cache_args == repo_name)
2948 if delete:
2945 if delete:
2949 log.debug('cache objects deleted for repo %s',
2946 log.debug('cache objects deleted for repo %s',
2950 safe_str(repo_name))
2947 safe_str(repo_name))
2951 qry.delete()
2948 qry.delete()
2952 else:
2949 else:
2953 log.debug('cache objects marked as invalid for repo %s',
2950 log.debug('cache objects marked as invalid for repo %s',
2954 safe_str(repo_name))
2951 safe_str(repo_name))
2955 qry.update({"cache_active": False})
2952 qry.update({"cache_active": False})
2956
2953
2957 Session().commit()
2954 Session().commit()
2958 except Exception:
2955 except Exception:
2959 log.exception(
2956 log.exception(
2960 'Cache key invalidation failed for repository %s',
2957 'Cache key invalidation failed for repository %s',
2961 safe_str(repo_name))
2958 safe_str(repo_name))
2962 Session().rollback()
2959 Session().rollback()
2963
2960
2964 @classmethod
2961 @classmethod
2965 def get_active_cache(cls, cache_key):
2962 def get_active_cache(cls, cache_key):
2966 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2963 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
2967 if inv_obj:
2964 if inv_obj:
2968 return inv_obj
2965 return inv_obj
2969 return None
2966 return None
2970
2967
2971 @classmethod
2968 @classmethod
2972 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2969 def repo_context_cache(cls, compute_func, repo_name, cache_type,
2973 thread_scoped=False):
2970 thread_scoped=False):
2974 """
2971 """
2975 @cache_region('long_term')
2972 @cache_region('long_term')
2976 def _heavy_calculation(cache_key):
2973 def _heavy_calculation(cache_key):
2977 return 'result'
2974 return 'result'
2978
2975
2979 cache_context = CacheKey.repo_context_cache(
2976 cache_context = CacheKey.repo_context_cache(
2980 _heavy_calculation, repo_name, cache_type)
2977 _heavy_calculation, repo_name, cache_type)
2981
2978
2982 with cache_context as context:
2979 with cache_context as context:
2983 context.invalidate()
2980 context.invalidate()
2984 computed = context.compute()
2981 computed = context.compute()
2985
2982
2986 assert computed == 'result'
2983 assert computed == 'result'
2987 """
2984 """
2988 from rhodecode.lib import caches
2985 from rhodecode.lib import caches
2989 return caches.InvalidationContext(
2986 return caches.InvalidationContext(
2990 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2987 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
2991
2988
2992
2989
2993 class ChangesetComment(Base, BaseModel):
2990 class ChangesetComment(Base, BaseModel):
2994 __tablename__ = 'changeset_comments'
2991 __tablename__ = 'changeset_comments'
2995 __table_args__ = (
2992 __table_args__ = (
2996 Index('cc_revision_idx', 'revision'),
2993 Index('cc_revision_idx', 'revision'),
2997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2994 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2995 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2999 )
2996 )
3000
2997
3001 COMMENT_OUTDATED = u'comment_outdated'
2998 COMMENT_OUTDATED = u'comment_outdated'
3002 COMMENT_TYPE_NOTE = u'note'
2999 COMMENT_TYPE_NOTE = u'note'
3003 COMMENT_TYPE_TODO = u'todo'
3000 COMMENT_TYPE_TODO = u'todo'
3004 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3001 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3005
3002
3006 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3003 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3007 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3004 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3008 revision = Column('revision', String(40), nullable=True)
3005 revision = Column('revision', String(40), nullable=True)
3009 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3006 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3010 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3007 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3011 line_no = Column('line_no', Unicode(10), nullable=True)
3008 line_no = Column('line_no', Unicode(10), nullable=True)
3012 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3009 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3013 f_path = Column('f_path', Unicode(1000), nullable=True)
3010 f_path = Column('f_path', Unicode(1000), nullable=True)
3014 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3011 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3015 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3012 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3016 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3013 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3017 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3014 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3018 renderer = Column('renderer', Unicode(64), nullable=True)
3015 renderer = Column('renderer', Unicode(64), nullable=True)
3019 display_state = Column('display_state', Unicode(128), nullable=True)
3016 display_state = Column('display_state', Unicode(128), nullable=True)
3020
3017
3021 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3018 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3022 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3019 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3023 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3020 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3024 author = relationship('User', lazy='joined')
3021 author = relationship('User', lazy='joined')
3025 repo = relationship('Repository')
3022 repo = relationship('Repository')
3026 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3023 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3027 pull_request = relationship('PullRequest', lazy='joined')
3024 pull_request = relationship('PullRequest', lazy='joined')
3028 pull_request_version = relationship('PullRequestVersion')
3025 pull_request_version = relationship('PullRequestVersion')
3029
3026
3030 @classmethod
3027 @classmethod
3031 def get_users(cls, revision=None, pull_request_id=None):
3028 def get_users(cls, revision=None, pull_request_id=None):
3032 """
3029 """
3033 Returns user associated with this ChangesetComment. ie those
3030 Returns user associated with this ChangesetComment. ie those
3034 who actually commented
3031 who actually commented
3035
3032
3036 :param cls:
3033 :param cls:
3037 :param revision:
3034 :param revision:
3038 """
3035 """
3039 q = Session().query(User)\
3036 q = Session().query(User)\
3040 .join(ChangesetComment.author)
3037 .join(ChangesetComment.author)
3041 if revision:
3038 if revision:
3042 q = q.filter(cls.revision == revision)
3039 q = q.filter(cls.revision == revision)
3043 elif pull_request_id:
3040 elif pull_request_id:
3044 q = q.filter(cls.pull_request_id == pull_request_id)
3041 q = q.filter(cls.pull_request_id == pull_request_id)
3045 return q.all()
3042 return q.all()
3046
3043
3047 @classmethod
3044 @classmethod
3048 def get_index_from_version(cls, pr_version, versions):
3045 def get_index_from_version(cls, pr_version, versions):
3049 num_versions = [x.pull_request_version_id for x in versions]
3046 num_versions = [x.pull_request_version_id for x in versions]
3050 try:
3047 try:
3051 return num_versions.index(pr_version) +1
3048 return num_versions.index(pr_version) +1
3052 except (IndexError, ValueError):
3049 except (IndexError, ValueError):
3053 return
3050 return
3054
3051
3055 @property
3052 @property
3056 def outdated(self):
3053 def outdated(self):
3057 return self.display_state == self.COMMENT_OUTDATED
3054 return self.display_state == self.COMMENT_OUTDATED
3058
3055
3059 def outdated_at_version(self, version):
3056 def outdated_at_version(self, version):
3060 """
3057 """
3061 Checks if comment is outdated for given pull request version
3058 Checks if comment is outdated for given pull request version
3062 """
3059 """
3063 return self.outdated and self.pull_request_version_id != version
3060 return self.outdated and self.pull_request_version_id != version
3064
3061
3065 def older_than_version(self, version):
3062 def older_than_version(self, version):
3066 """
3063 """
3067 Checks if comment is made from previous version than given
3064 Checks if comment is made from previous version than given
3068 """
3065 """
3069 if version is None:
3066 if version is None:
3070 return self.pull_request_version_id is not None
3067 return self.pull_request_version_id is not None
3071
3068
3072 return self.pull_request_version_id < version
3069 return self.pull_request_version_id < version
3073
3070
3074 @property
3071 @property
3075 def resolved(self):
3072 def resolved(self):
3076 return self.resolved_by[0] if self.resolved_by else None
3073 return self.resolved_by[0] if self.resolved_by else None
3077
3074
3078 @property
3075 @property
3079 def is_todo(self):
3076 def is_todo(self):
3080 return self.comment_type == self.COMMENT_TYPE_TODO
3077 return self.comment_type == self.COMMENT_TYPE_TODO
3081
3078
3082 def get_index_version(self, versions):
3079 def get_index_version(self, versions):
3083 return self.get_index_from_version(
3080 return self.get_index_from_version(
3084 self.pull_request_version_id, versions)
3081 self.pull_request_version_id, versions)
3085
3082
3086 def render(self, mentions=False):
3083 def render(self, mentions=False):
3087 from rhodecode.lib import helpers as h
3084 from rhodecode.lib import helpers as h
3088 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3085 return h.render(self.text, renderer=self.renderer, mentions=mentions)
3089
3086
3090 def __repr__(self):
3087 def __repr__(self):
3091 if self.comment_id:
3088 if self.comment_id:
3092 return '<DB:Comment #%s>' % self.comment_id
3089 return '<DB:Comment #%s>' % self.comment_id
3093 else:
3090 else:
3094 return '<DB:Comment at %#x>' % id(self)
3091 return '<DB:Comment at %#x>' % id(self)
3095
3092
3096
3093
3097 class ChangesetStatus(Base, BaseModel):
3094 class ChangesetStatus(Base, BaseModel):
3098 __tablename__ = 'changeset_statuses'
3095 __tablename__ = 'changeset_statuses'
3099 __table_args__ = (
3096 __table_args__ = (
3100 Index('cs_revision_idx', 'revision'),
3097 Index('cs_revision_idx', 'revision'),
3101 Index('cs_version_idx', 'version'),
3098 Index('cs_version_idx', 'version'),
3102 UniqueConstraint('repo_id', 'revision', 'version'),
3099 UniqueConstraint('repo_id', 'revision', 'version'),
3103 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3104 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3101 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3105 )
3102 )
3106 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3103 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3107 STATUS_APPROVED = 'approved'
3104 STATUS_APPROVED = 'approved'
3108 STATUS_REJECTED = 'rejected'
3105 STATUS_REJECTED = 'rejected'
3109 STATUS_UNDER_REVIEW = 'under_review'
3106 STATUS_UNDER_REVIEW = 'under_review'
3110
3107
3111 STATUSES = [
3108 STATUSES = [
3112 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3109 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3113 (STATUS_APPROVED, _("Approved")),
3110 (STATUS_APPROVED, _("Approved")),
3114 (STATUS_REJECTED, _("Rejected")),
3111 (STATUS_REJECTED, _("Rejected")),
3115 (STATUS_UNDER_REVIEW, _("Under Review")),
3112 (STATUS_UNDER_REVIEW, _("Under Review")),
3116 ]
3113 ]
3117
3114
3118 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3115 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3119 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3116 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3120 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3117 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3121 revision = Column('revision', String(40), nullable=False)
3118 revision = Column('revision', String(40), nullable=False)
3122 status = Column('status', String(128), nullable=False, default=DEFAULT)
3119 status = Column('status', String(128), nullable=False, default=DEFAULT)
3123 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3120 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3124 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3121 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3125 version = Column('version', Integer(), nullable=False, default=0)
3122 version = Column('version', Integer(), nullable=False, default=0)
3126 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3123 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3127
3124
3128 author = relationship('User', lazy='joined')
3125 author = relationship('User', lazy='joined')
3129 repo = relationship('Repository')
3126 repo = relationship('Repository')
3130 comment = relationship('ChangesetComment', lazy='joined')
3127 comment = relationship('ChangesetComment', lazy='joined')
3131 pull_request = relationship('PullRequest', lazy='joined')
3128 pull_request = relationship('PullRequest', lazy='joined')
3132
3129
3133 def __unicode__(self):
3130 def __unicode__(self):
3134 return u"<%s('%s[v%s]:%s')>" % (
3131 return u"<%s('%s[v%s]:%s')>" % (
3135 self.__class__.__name__,
3132 self.__class__.__name__,
3136 self.status, self.version, self.author
3133 self.status, self.version, self.author
3137 )
3134 )
3138
3135
3139 @classmethod
3136 @classmethod
3140 def get_status_lbl(cls, value):
3137 def get_status_lbl(cls, value):
3141 return dict(cls.STATUSES).get(value)
3138 return dict(cls.STATUSES).get(value)
3142
3139
3143 @property
3140 @property
3144 def status_lbl(self):
3141 def status_lbl(self):
3145 return ChangesetStatus.get_status_lbl(self.status)
3142 return ChangesetStatus.get_status_lbl(self.status)
3146
3143
3147
3144
3148 class _PullRequestBase(BaseModel):
3145 class _PullRequestBase(BaseModel):
3149 """
3146 """
3150 Common attributes of pull request and version entries.
3147 Common attributes of pull request and version entries.
3151 """
3148 """
3152
3149
3153 # .status values
3150 # .status values
3154 STATUS_NEW = u'new'
3151 STATUS_NEW = u'new'
3155 STATUS_OPEN = u'open'
3152 STATUS_OPEN = u'open'
3156 STATUS_CLOSED = u'closed'
3153 STATUS_CLOSED = u'closed'
3157
3154
3158 title = Column('title', Unicode(255), nullable=True)
3155 title = Column('title', Unicode(255), nullable=True)
3159 description = Column(
3156 description = Column(
3160 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3157 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3161 nullable=True)
3158 nullable=True)
3162 # new/open/closed status of pull request (not approve/reject/etc)
3159 # new/open/closed status of pull request (not approve/reject/etc)
3163 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3160 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3164 created_on = Column(
3161 created_on = Column(
3165 'created_on', DateTime(timezone=False), nullable=False,
3162 'created_on', DateTime(timezone=False), nullable=False,
3166 default=datetime.datetime.now)
3163 default=datetime.datetime.now)
3167 updated_on = Column(
3164 updated_on = Column(
3168 'updated_on', DateTime(timezone=False), nullable=False,
3165 'updated_on', DateTime(timezone=False), nullable=False,
3169 default=datetime.datetime.now)
3166 default=datetime.datetime.now)
3170
3167
3171 @declared_attr
3168 @declared_attr
3172 def user_id(cls):
3169 def user_id(cls):
3173 return Column(
3170 return Column(
3174 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3171 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3175 unique=None)
3172 unique=None)
3176
3173
3177 # 500 revisions max
3174 # 500 revisions max
3178 _revisions = Column(
3175 _revisions = Column(
3179 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3176 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3180
3177
3181 @declared_attr
3178 @declared_attr
3182 def source_repo_id(cls):
3179 def source_repo_id(cls):
3183 # TODO: dan: rename column to source_repo_id
3180 # TODO: dan: rename column to source_repo_id
3184 return Column(
3181 return Column(
3185 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3182 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3186 nullable=False)
3183 nullable=False)
3187
3184
3188 source_ref = Column('org_ref', Unicode(255), nullable=False)
3185 source_ref = Column('org_ref', Unicode(255), nullable=False)
3189
3186
3190 @declared_attr
3187 @declared_attr
3191 def target_repo_id(cls):
3188 def target_repo_id(cls):
3192 # TODO: dan: rename column to target_repo_id
3189 # TODO: dan: rename column to target_repo_id
3193 return Column(
3190 return Column(
3194 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3191 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3195 nullable=False)
3192 nullable=False)
3196
3193
3197 target_ref = Column('other_ref', Unicode(255), nullable=False)
3194 target_ref = Column('other_ref', Unicode(255), nullable=False)
3198 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3195 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3199
3196
3200 # TODO: dan: rename column to last_merge_source_rev
3197 # TODO: dan: rename column to last_merge_source_rev
3201 _last_merge_source_rev = Column(
3198 _last_merge_source_rev = Column(
3202 'last_merge_org_rev', String(40), nullable=True)
3199 'last_merge_org_rev', String(40), nullable=True)
3203 # TODO: dan: rename column to last_merge_target_rev
3200 # TODO: dan: rename column to last_merge_target_rev
3204 _last_merge_target_rev = Column(
3201 _last_merge_target_rev = Column(
3205 'last_merge_other_rev', String(40), nullable=True)
3202 'last_merge_other_rev', String(40), nullable=True)
3206 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3203 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3207 merge_rev = Column('merge_rev', String(40), nullable=True)
3204 merge_rev = Column('merge_rev', String(40), nullable=True)
3208
3205
3209 @hybrid_property
3206 @hybrid_property
3210 def revisions(self):
3207 def revisions(self):
3211 return self._revisions.split(':') if self._revisions else []
3208 return self._revisions.split(':') if self._revisions else []
3212
3209
3213 @revisions.setter
3210 @revisions.setter
3214 def revisions(self, val):
3211 def revisions(self, val):
3215 self._revisions = ':'.join(val)
3212 self._revisions = ':'.join(val)
3216
3213
3217 @declared_attr
3214 @declared_attr
3218 def author(cls):
3215 def author(cls):
3219 return relationship('User', lazy='joined')
3216 return relationship('User', lazy='joined')
3220
3217
3221 @declared_attr
3218 @declared_attr
3222 def source_repo(cls):
3219 def source_repo(cls):
3223 return relationship(
3220 return relationship(
3224 'Repository',
3221 'Repository',
3225 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3222 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3226
3223
3227 @property
3224 @property
3228 def source_ref_parts(self):
3225 def source_ref_parts(self):
3229 return self.unicode_to_reference(self.source_ref)
3226 return self.unicode_to_reference(self.source_ref)
3230
3227
3231 @declared_attr
3228 @declared_attr
3232 def target_repo(cls):
3229 def target_repo(cls):
3233 return relationship(
3230 return relationship(
3234 'Repository',
3231 'Repository',
3235 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3232 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3236
3233
3237 @property
3234 @property
3238 def target_ref_parts(self):
3235 def target_ref_parts(self):
3239 return self.unicode_to_reference(self.target_ref)
3236 return self.unicode_to_reference(self.target_ref)
3240
3237
3241 @property
3238 @property
3242 def shadow_merge_ref(self):
3239 def shadow_merge_ref(self):
3243 return self.unicode_to_reference(self._shadow_merge_ref)
3240 return self.unicode_to_reference(self._shadow_merge_ref)
3244
3241
3245 @shadow_merge_ref.setter
3242 @shadow_merge_ref.setter
3246 def shadow_merge_ref(self, ref):
3243 def shadow_merge_ref(self, ref):
3247 self._shadow_merge_ref = self.reference_to_unicode(ref)
3244 self._shadow_merge_ref = self.reference_to_unicode(ref)
3248
3245
3249 def unicode_to_reference(self, raw):
3246 def unicode_to_reference(self, raw):
3250 """
3247 """
3251 Convert a unicode (or string) to a reference object.
3248 Convert a unicode (or string) to a reference object.
3252 If unicode evaluates to False it returns None.
3249 If unicode evaluates to False it returns None.
3253 """
3250 """
3254 if raw:
3251 if raw:
3255 refs = raw.split(':')
3252 refs = raw.split(':')
3256 return Reference(*refs)
3253 return Reference(*refs)
3257 else:
3254 else:
3258 return None
3255 return None
3259
3256
3260 def reference_to_unicode(self, ref):
3257 def reference_to_unicode(self, ref):
3261 """
3258 """
3262 Convert a reference object to unicode.
3259 Convert a reference object to unicode.
3263 If reference is None it returns None.
3260 If reference is None it returns None.
3264 """
3261 """
3265 if ref:
3262 if ref:
3266 return u':'.join(ref)
3263 return u':'.join(ref)
3267 else:
3264 else:
3268 return None
3265 return None
3269
3266
3270 def get_api_data(self):
3267 def get_api_data(self):
3271 from rhodecode.model.pull_request import PullRequestModel
3268 from rhodecode.model.pull_request import PullRequestModel
3272 pull_request = self
3269 pull_request = self
3273 merge_status = PullRequestModel().merge_status(pull_request)
3270 merge_status = PullRequestModel().merge_status(pull_request)
3274
3271
3275 pull_request_url = url(
3272 pull_request_url = url(
3276 'pullrequest_show', repo_name=self.target_repo.repo_name,
3273 'pullrequest_show', repo_name=self.target_repo.repo_name,
3277 pull_request_id=self.pull_request_id, qualified=True)
3274 pull_request_id=self.pull_request_id, qualified=True)
3278
3275
3279 merge_data = {
3276 merge_data = {
3280 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3277 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3281 'reference': (
3278 'reference': (
3282 pull_request.shadow_merge_ref._asdict()
3279 pull_request.shadow_merge_ref._asdict()
3283 if pull_request.shadow_merge_ref else None),
3280 if pull_request.shadow_merge_ref else None),
3284 }
3281 }
3285
3282
3286 data = {
3283 data = {
3287 'pull_request_id': pull_request.pull_request_id,
3284 'pull_request_id': pull_request.pull_request_id,
3288 'url': pull_request_url,
3285 'url': pull_request_url,
3289 'title': pull_request.title,
3286 'title': pull_request.title,
3290 'description': pull_request.description,
3287 'description': pull_request.description,
3291 'status': pull_request.status,
3288 'status': pull_request.status,
3292 'created_on': pull_request.created_on,
3289 'created_on': pull_request.created_on,
3293 'updated_on': pull_request.updated_on,
3290 'updated_on': pull_request.updated_on,
3294 'commit_ids': pull_request.revisions,
3291 'commit_ids': pull_request.revisions,
3295 'review_status': pull_request.calculated_review_status(),
3292 'review_status': pull_request.calculated_review_status(),
3296 'mergeable': {
3293 'mergeable': {
3297 'status': merge_status[0],
3294 'status': merge_status[0],
3298 'message': unicode(merge_status[1]),
3295 'message': unicode(merge_status[1]),
3299 },
3296 },
3300 'source': {
3297 'source': {
3301 'clone_url': pull_request.source_repo.clone_url(),
3298 'clone_url': pull_request.source_repo.clone_url(),
3302 'repository': pull_request.source_repo.repo_name,
3299 'repository': pull_request.source_repo.repo_name,
3303 'reference': {
3300 'reference': {
3304 'name': pull_request.source_ref_parts.name,
3301 'name': pull_request.source_ref_parts.name,
3305 'type': pull_request.source_ref_parts.type,
3302 'type': pull_request.source_ref_parts.type,
3306 'commit_id': pull_request.source_ref_parts.commit_id,
3303 'commit_id': pull_request.source_ref_parts.commit_id,
3307 },
3304 },
3308 },
3305 },
3309 'target': {
3306 'target': {
3310 'clone_url': pull_request.target_repo.clone_url(),
3307 'clone_url': pull_request.target_repo.clone_url(),
3311 'repository': pull_request.target_repo.repo_name,
3308 'repository': pull_request.target_repo.repo_name,
3312 'reference': {
3309 'reference': {
3313 'name': pull_request.target_ref_parts.name,
3310 'name': pull_request.target_ref_parts.name,
3314 'type': pull_request.target_ref_parts.type,
3311 'type': pull_request.target_ref_parts.type,
3315 'commit_id': pull_request.target_ref_parts.commit_id,
3312 'commit_id': pull_request.target_ref_parts.commit_id,
3316 },
3313 },
3317 },
3314 },
3318 'merge': merge_data,
3315 'merge': merge_data,
3319 'author': pull_request.author.get_api_data(include_secrets=False,
3316 'author': pull_request.author.get_api_data(include_secrets=False,
3320 details='basic'),
3317 details='basic'),
3321 'reviewers': [
3318 'reviewers': [
3322 {
3319 {
3323 'user': reviewer.get_api_data(include_secrets=False,
3320 'user': reviewer.get_api_data(include_secrets=False,
3324 details='basic'),
3321 details='basic'),
3325 'reasons': reasons,
3322 'reasons': reasons,
3326 'review_status': st[0][1].status if st else 'not_reviewed',
3323 'review_status': st[0][1].status if st else 'not_reviewed',
3327 }
3324 }
3328 for reviewer, reasons, st in pull_request.reviewers_statuses()
3325 for reviewer, reasons, st in pull_request.reviewers_statuses()
3329 ]
3326 ]
3330 }
3327 }
3331
3328
3332 return data
3329 return data
3333
3330
3334
3331
3335 class PullRequest(Base, _PullRequestBase):
3332 class PullRequest(Base, _PullRequestBase):
3336 __tablename__ = 'pull_requests'
3333 __tablename__ = 'pull_requests'
3337 __table_args__ = (
3334 __table_args__ = (
3338 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3335 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3339 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3336 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3340 )
3337 )
3341
3338
3342 pull_request_id = Column(
3339 pull_request_id = Column(
3343 'pull_request_id', Integer(), nullable=False, primary_key=True)
3340 'pull_request_id', Integer(), nullable=False, primary_key=True)
3344
3341
3345 def __repr__(self):
3342 def __repr__(self):
3346 if self.pull_request_id:
3343 if self.pull_request_id:
3347 return '<DB:PullRequest #%s>' % self.pull_request_id
3344 return '<DB:PullRequest #%s>' % self.pull_request_id
3348 else:
3345 else:
3349 return '<DB:PullRequest at %#x>' % id(self)
3346 return '<DB:PullRequest at %#x>' % id(self)
3350
3347
3351 reviewers = relationship('PullRequestReviewers',
3348 reviewers = relationship('PullRequestReviewers',
3352 cascade="all, delete, delete-orphan")
3349 cascade="all, delete, delete-orphan")
3353 statuses = relationship('ChangesetStatus')
3350 statuses = relationship('ChangesetStatus')
3354 comments = relationship('ChangesetComment',
3351 comments = relationship('ChangesetComment',
3355 cascade="all, delete, delete-orphan")
3352 cascade="all, delete, delete-orphan")
3356 versions = relationship('PullRequestVersion',
3353 versions = relationship('PullRequestVersion',
3357 cascade="all, delete, delete-orphan",
3354 cascade="all, delete, delete-orphan",
3358 lazy='dynamic')
3355 lazy='dynamic')
3359
3356
3360 @classmethod
3357 @classmethod
3361 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3358 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3362 internal_methods=None):
3359 internal_methods=None):
3363
3360
3364 class PullRequestDisplay(object):
3361 class PullRequestDisplay(object):
3365 """
3362 """
3366 Special object wrapper for showing PullRequest data via Versions
3363 Special object wrapper for showing PullRequest data via Versions
3367 It mimics PR object as close as possible. This is read only object
3364 It mimics PR object as close as possible. This is read only object
3368 just for display
3365 just for display
3369 """
3366 """
3370
3367
3371 def __init__(self, attrs, internal=None):
3368 def __init__(self, attrs, internal=None):
3372 self.attrs = attrs
3369 self.attrs = attrs
3373 # internal have priority over the given ones via attrs
3370 # internal have priority over the given ones via attrs
3374 self.internal = internal or ['versions']
3371 self.internal = internal or ['versions']
3375
3372
3376 def __getattr__(self, item):
3373 def __getattr__(self, item):
3377 if item in self.internal:
3374 if item in self.internal:
3378 return getattr(self, item)
3375 return getattr(self, item)
3379 try:
3376 try:
3380 return self.attrs[item]
3377 return self.attrs[item]
3381 except KeyError:
3378 except KeyError:
3382 raise AttributeError(
3379 raise AttributeError(
3383 '%s object has no attribute %s' % (self, item))
3380 '%s object has no attribute %s' % (self, item))
3384
3381
3385 def __repr__(self):
3382 def __repr__(self):
3386 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3383 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3387
3384
3388 def versions(self):
3385 def versions(self):
3389 return pull_request_obj.versions.order_by(
3386 return pull_request_obj.versions.order_by(
3390 PullRequestVersion.pull_request_version_id).all()
3387 PullRequestVersion.pull_request_version_id).all()
3391
3388
3392 def is_closed(self):
3389 def is_closed(self):
3393 return pull_request_obj.is_closed()
3390 return pull_request_obj.is_closed()
3394
3391
3395 @property
3392 @property
3396 def pull_request_version_id(self):
3393 def pull_request_version_id(self):
3397 return getattr(pull_request_obj, 'pull_request_version_id', None)
3394 return getattr(pull_request_obj, 'pull_request_version_id', None)
3398
3395
3399 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3396 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3400
3397
3401 attrs.author = StrictAttributeDict(
3398 attrs.author = StrictAttributeDict(
3402 pull_request_obj.author.get_api_data())
3399 pull_request_obj.author.get_api_data())
3403 if pull_request_obj.target_repo:
3400 if pull_request_obj.target_repo:
3404 attrs.target_repo = StrictAttributeDict(
3401 attrs.target_repo = StrictAttributeDict(
3405 pull_request_obj.target_repo.get_api_data())
3402 pull_request_obj.target_repo.get_api_data())
3406 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3403 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3407
3404
3408 if pull_request_obj.source_repo:
3405 if pull_request_obj.source_repo:
3409 attrs.source_repo = StrictAttributeDict(
3406 attrs.source_repo = StrictAttributeDict(
3410 pull_request_obj.source_repo.get_api_data())
3407 pull_request_obj.source_repo.get_api_data())
3411 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3408 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3412
3409
3413 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3410 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3414 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3411 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3415 attrs.revisions = pull_request_obj.revisions
3412 attrs.revisions = pull_request_obj.revisions
3416
3413
3417 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3414 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3418
3415
3419 return PullRequestDisplay(attrs, internal=internal_methods)
3416 return PullRequestDisplay(attrs, internal=internal_methods)
3420
3417
3421 def is_closed(self):
3418 def is_closed(self):
3422 return self.status == self.STATUS_CLOSED
3419 return self.status == self.STATUS_CLOSED
3423
3420
3424 def __json__(self):
3421 def __json__(self):
3425 return {
3422 return {
3426 'revisions': self.revisions,
3423 'revisions': self.revisions,
3427 }
3424 }
3428
3425
3429 def calculated_review_status(self):
3426 def calculated_review_status(self):
3430 from rhodecode.model.changeset_status import ChangesetStatusModel
3427 from rhodecode.model.changeset_status import ChangesetStatusModel
3431 return ChangesetStatusModel().calculated_review_status(self)
3428 return ChangesetStatusModel().calculated_review_status(self)
3432
3429
3433 def reviewers_statuses(self):
3430 def reviewers_statuses(self):
3434 from rhodecode.model.changeset_status import ChangesetStatusModel
3431 from rhodecode.model.changeset_status import ChangesetStatusModel
3435 return ChangesetStatusModel().reviewers_statuses(self)
3432 return ChangesetStatusModel().reviewers_statuses(self)
3436
3433
3437 @property
3434 @property
3438 def workspace_id(self):
3435 def workspace_id(self):
3439 from rhodecode.model.pull_request import PullRequestModel
3436 from rhodecode.model.pull_request import PullRequestModel
3440 return PullRequestModel()._workspace_id(self)
3437 return PullRequestModel()._workspace_id(self)
3441
3438
3442 def get_shadow_repo(self):
3439 def get_shadow_repo(self):
3443 workspace_id = self.workspace_id
3440 workspace_id = self.workspace_id
3444 vcs_obj = self.target_repo.scm_instance()
3441 vcs_obj = self.target_repo.scm_instance()
3445 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3442 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3446 workspace_id)
3443 workspace_id)
3447 return vcs_obj._get_shadow_instance(shadow_repository_path)
3444 return vcs_obj._get_shadow_instance(shadow_repository_path)
3448
3445
3449
3446
3450 class PullRequestVersion(Base, _PullRequestBase):
3447 class PullRequestVersion(Base, _PullRequestBase):
3451 __tablename__ = 'pull_request_versions'
3448 __tablename__ = 'pull_request_versions'
3452 __table_args__ = (
3449 __table_args__ = (
3453 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3450 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3454 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3451 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3455 )
3452 )
3456
3453
3457 pull_request_version_id = Column(
3454 pull_request_version_id = Column(
3458 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3455 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3459 pull_request_id = Column(
3456 pull_request_id = Column(
3460 'pull_request_id', Integer(),
3457 'pull_request_id', Integer(),
3461 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3458 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3462 pull_request = relationship('PullRequest')
3459 pull_request = relationship('PullRequest')
3463
3460
3464 def __repr__(self):
3461 def __repr__(self):
3465 if self.pull_request_version_id:
3462 if self.pull_request_version_id:
3466 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3463 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3467 else:
3464 else:
3468 return '<DB:PullRequestVersion at %#x>' % id(self)
3465 return '<DB:PullRequestVersion at %#x>' % id(self)
3469
3466
3470 @property
3467 @property
3471 def reviewers(self):
3468 def reviewers(self):
3472 return self.pull_request.reviewers
3469 return self.pull_request.reviewers
3473
3470
3474 @property
3471 @property
3475 def versions(self):
3472 def versions(self):
3476 return self.pull_request.versions
3473 return self.pull_request.versions
3477
3474
3478 def is_closed(self):
3475 def is_closed(self):
3479 # calculate from original
3476 # calculate from original
3480 return self.pull_request.status == self.STATUS_CLOSED
3477 return self.pull_request.status == self.STATUS_CLOSED
3481
3478
3482 def calculated_review_status(self):
3479 def calculated_review_status(self):
3483 return self.pull_request.calculated_review_status()
3480 return self.pull_request.calculated_review_status()
3484
3481
3485 def reviewers_statuses(self):
3482 def reviewers_statuses(self):
3486 return self.pull_request.reviewers_statuses()
3483 return self.pull_request.reviewers_statuses()
3487
3484
3488
3485
3489 class PullRequestReviewers(Base, BaseModel):
3486 class PullRequestReviewers(Base, BaseModel):
3490 __tablename__ = 'pull_request_reviewers'
3487 __tablename__ = 'pull_request_reviewers'
3491 __table_args__ = (
3488 __table_args__ = (
3492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3489 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3493 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3490 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3494 )
3491 )
3495
3492
3496 def __init__(self, user=None, pull_request=None, reasons=None):
3493 def __init__(self, user=None, pull_request=None, reasons=None):
3497 self.user = user
3494 self.user = user
3498 self.pull_request = pull_request
3495 self.pull_request = pull_request
3499 self.reasons = reasons or []
3496 self.reasons = reasons or []
3500
3497
3501 @hybrid_property
3498 @hybrid_property
3502 def reasons(self):
3499 def reasons(self):
3503 if not self._reasons:
3500 if not self._reasons:
3504 return []
3501 return []
3505 return self._reasons
3502 return self._reasons
3506
3503
3507 @reasons.setter
3504 @reasons.setter
3508 def reasons(self, val):
3505 def reasons(self, val):
3509 val = val or []
3506 val = val or []
3510 if any(not isinstance(x, basestring) for x in val):
3507 if any(not isinstance(x, basestring) for x in val):
3511 raise Exception('invalid reasons type, must be list of strings')
3508 raise Exception('invalid reasons type, must be list of strings')
3512 self._reasons = val
3509 self._reasons = val
3513
3510
3514 pull_requests_reviewers_id = Column(
3511 pull_requests_reviewers_id = Column(
3515 'pull_requests_reviewers_id', Integer(), nullable=False,
3512 'pull_requests_reviewers_id', Integer(), nullable=False,
3516 primary_key=True)
3513 primary_key=True)
3517 pull_request_id = Column(
3514 pull_request_id = Column(
3518 "pull_request_id", Integer(),
3515 "pull_request_id", Integer(),
3519 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3516 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3520 user_id = Column(
3517 user_id = Column(
3521 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3518 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3522 _reasons = Column(
3519 _reasons = Column(
3523 'reason', MutationList.as_mutable(
3520 'reason', MutationList.as_mutable(
3524 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3521 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3525
3522
3526 user = relationship('User')
3523 user = relationship('User')
3527 pull_request = relationship('PullRequest')
3524 pull_request = relationship('PullRequest')
3528
3525
3529
3526
3530 class Notification(Base, BaseModel):
3527 class Notification(Base, BaseModel):
3531 __tablename__ = 'notifications'
3528 __tablename__ = 'notifications'
3532 __table_args__ = (
3529 __table_args__ = (
3533 Index('notification_type_idx', 'type'),
3530 Index('notification_type_idx', 'type'),
3534 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3535 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3532 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3536 )
3533 )
3537
3534
3538 TYPE_CHANGESET_COMMENT = u'cs_comment'
3535 TYPE_CHANGESET_COMMENT = u'cs_comment'
3539 TYPE_MESSAGE = u'message'
3536 TYPE_MESSAGE = u'message'
3540 TYPE_MENTION = u'mention'
3537 TYPE_MENTION = u'mention'
3541 TYPE_REGISTRATION = u'registration'
3538 TYPE_REGISTRATION = u'registration'
3542 TYPE_PULL_REQUEST = u'pull_request'
3539 TYPE_PULL_REQUEST = u'pull_request'
3543 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3540 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3544
3541
3545 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3542 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3546 subject = Column('subject', Unicode(512), nullable=True)
3543 subject = Column('subject', Unicode(512), nullable=True)
3547 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3544 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3548 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3545 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3549 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3546 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3550 type_ = Column('type', Unicode(255))
3547 type_ = Column('type', Unicode(255))
3551
3548
3552 created_by_user = relationship('User')
3549 created_by_user = relationship('User')
3553 notifications_to_users = relationship('UserNotification', lazy='joined',
3550 notifications_to_users = relationship('UserNotification', lazy='joined',
3554 cascade="all, delete, delete-orphan")
3551 cascade="all, delete, delete-orphan")
3555
3552
3556 @property
3553 @property
3557 def recipients(self):
3554 def recipients(self):
3558 return [x.user for x in UserNotification.query()\
3555 return [x.user for x in UserNotification.query()\
3559 .filter(UserNotification.notification == self)\
3556 .filter(UserNotification.notification == self)\
3560 .order_by(UserNotification.user_id.asc()).all()]
3557 .order_by(UserNotification.user_id.asc()).all()]
3561
3558
3562 @classmethod
3559 @classmethod
3563 def create(cls, created_by, subject, body, recipients, type_=None):
3560 def create(cls, created_by, subject, body, recipients, type_=None):
3564 if type_ is None:
3561 if type_ is None:
3565 type_ = Notification.TYPE_MESSAGE
3562 type_ = Notification.TYPE_MESSAGE
3566
3563
3567 notification = cls()
3564 notification = cls()
3568 notification.created_by_user = created_by
3565 notification.created_by_user = created_by
3569 notification.subject = subject
3566 notification.subject = subject
3570 notification.body = body
3567 notification.body = body
3571 notification.type_ = type_
3568 notification.type_ = type_
3572 notification.created_on = datetime.datetime.now()
3569 notification.created_on = datetime.datetime.now()
3573
3570
3574 for u in recipients:
3571 for u in recipients:
3575 assoc = UserNotification()
3572 assoc = UserNotification()
3576 assoc.notification = notification
3573 assoc.notification = notification
3577
3574
3578 # if created_by is inside recipients mark his notification
3575 # if created_by is inside recipients mark his notification
3579 # as read
3576 # as read
3580 if u.user_id == created_by.user_id:
3577 if u.user_id == created_by.user_id:
3581 assoc.read = True
3578 assoc.read = True
3582
3579
3583 u.notifications.append(assoc)
3580 u.notifications.append(assoc)
3584 Session().add(notification)
3581 Session().add(notification)
3585
3582
3586 return notification
3583 return notification
3587
3584
3588 @property
3585 @property
3589 def description(self):
3586 def description(self):
3590 from rhodecode.model.notification import NotificationModel
3587 from rhodecode.model.notification import NotificationModel
3591 return NotificationModel().make_description(self)
3588 return NotificationModel().make_description(self)
3592
3589
3593
3590
3594 class UserNotification(Base, BaseModel):
3591 class UserNotification(Base, BaseModel):
3595 __tablename__ = 'user_to_notification'
3592 __tablename__ = 'user_to_notification'
3596 __table_args__ = (
3593 __table_args__ = (
3597 UniqueConstraint('user_id', 'notification_id'),
3594 UniqueConstraint('user_id', 'notification_id'),
3598 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3595 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3599 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3596 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3600 )
3597 )
3601 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3598 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3602 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3599 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3603 read = Column('read', Boolean, default=False)
3600 read = Column('read', Boolean, default=False)
3604 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3601 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3605
3602
3606 user = relationship('User', lazy="joined")
3603 user = relationship('User', lazy="joined")
3607 notification = relationship('Notification', lazy="joined",
3604 notification = relationship('Notification', lazy="joined",
3608 order_by=lambda: Notification.created_on.desc(),)
3605 order_by=lambda: Notification.created_on.desc(),)
3609
3606
3610 def mark_as_read(self):
3607 def mark_as_read(self):
3611 self.read = True
3608 self.read = True
3612 Session().add(self)
3609 Session().add(self)
3613
3610
3614
3611
3615 class Gist(Base, BaseModel):
3612 class Gist(Base, BaseModel):
3616 __tablename__ = 'gists'
3613 __tablename__ = 'gists'
3617 __table_args__ = (
3614 __table_args__ = (
3618 Index('g_gist_access_id_idx', 'gist_access_id'),
3615 Index('g_gist_access_id_idx', 'gist_access_id'),
3619 Index('g_created_on_idx', 'created_on'),
3616 Index('g_created_on_idx', 'created_on'),
3620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3617 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3621 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3618 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3622 )
3619 )
3623 GIST_PUBLIC = u'public'
3620 GIST_PUBLIC = u'public'
3624 GIST_PRIVATE = u'private'
3621 GIST_PRIVATE = u'private'
3625 DEFAULT_FILENAME = u'gistfile1.txt'
3622 DEFAULT_FILENAME = u'gistfile1.txt'
3626
3623
3627 ACL_LEVEL_PUBLIC = u'acl_public'
3624 ACL_LEVEL_PUBLIC = u'acl_public'
3628 ACL_LEVEL_PRIVATE = u'acl_private'
3625 ACL_LEVEL_PRIVATE = u'acl_private'
3629
3626
3630 gist_id = Column('gist_id', Integer(), primary_key=True)
3627 gist_id = Column('gist_id', Integer(), primary_key=True)
3631 gist_access_id = Column('gist_access_id', Unicode(250))
3628 gist_access_id = Column('gist_access_id', Unicode(250))
3632 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3629 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3633 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3630 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3634 gist_expires = Column('gist_expires', Float(53), nullable=False)
3631 gist_expires = Column('gist_expires', Float(53), nullable=False)
3635 gist_type = Column('gist_type', Unicode(128), nullable=False)
3632 gist_type = Column('gist_type', Unicode(128), nullable=False)
3636 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3633 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3637 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3634 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3638 acl_level = Column('acl_level', Unicode(128), nullable=True)
3635 acl_level = Column('acl_level', Unicode(128), nullable=True)
3639
3636
3640 owner = relationship('User')
3637 owner = relationship('User')
3641
3638
3642 def __repr__(self):
3639 def __repr__(self):
3643 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3640 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3644
3641
3645 @classmethod
3642 @classmethod
3646 def get_or_404(cls, id_, pyramid_exc=False):
3643 def get_or_404(cls, id_, pyramid_exc=False):
3647
3644
3648 if pyramid_exc:
3645 if pyramid_exc:
3649 from pyramid.httpexceptions import HTTPNotFound
3646 from pyramid.httpexceptions import HTTPNotFound
3650 else:
3647 else:
3651 from webob.exc import HTTPNotFound
3648 from webob.exc import HTTPNotFound
3652
3649
3653 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3650 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3654 if not res:
3651 if not res:
3655 raise HTTPNotFound
3652 raise HTTPNotFound
3656 return res
3653 return res
3657
3654
3658 @classmethod
3655 @classmethod
3659 def get_by_access_id(cls, gist_access_id):
3656 def get_by_access_id(cls, gist_access_id):
3660 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3657 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3661
3658
3662 def gist_url(self):
3659 def gist_url(self):
3663 import rhodecode
3660 import rhodecode
3664 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3661 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3665 if alias_url:
3662 if alias_url:
3666 return alias_url.replace('{gistid}', self.gist_access_id)
3663 return alias_url.replace('{gistid}', self.gist_access_id)
3667
3664
3668 return url('gist', gist_id=self.gist_access_id, qualified=True)
3665 return url('gist', gist_id=self.gist_access_id, qualified=True)
3669
3666
3670 @classmethod
3667 @classmethod
3671 def base_path(cls):
3668 def base_path(cls):
3672 """
3669 """
3673 Returns base path when all gists are stored
3670 Returns base path when all gists are stored
3674
3671
3675 :param cls:
3672 :param cls:
3676 """
3673 """
3677 from rhodecode.model.gist import GIST_STORE_LOC
3674 from rhodecode.model.gist import GIST_STORE_LOC
3678 q = Session().query(RhodeCodeUi)\
3675 q = Session().query(RhodeCodeUi)\
3679 .filter(RhodeCodeUi.ui_key == URL_SEP)
3676 .filter(RhodeCodeUi.ui_key == URL_SEP)
3680 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3677 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3681 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3678 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3682
3679
3683 def get_api_data(self):
3680 def get_api_data(self):
3684 """
3681 """
3685 Common function for generating gist related data for API
3682 Common function for generating gist related data for API
3686 """
3683 """
3687 gist = self
3684 gist = self
3688 data = {
3685 data = {
3689 'gist_id': gist.gist_id,
3686 'gist_id': gist.gist_id,
3690 'type': gist.gist_type,
3687 'type': gist.gist_type,
3691 'access_id': gist.gist_access_id,
3688 'access_id': gist.gist_access_id,
3692 'description': gist.gist_description,
3689 'description': gist.gist_description,
3693 'url': gist.gist_url(),
3690 'url': gist.gist_url(),
3694 'expires': gist.gist_expires,
3691 'expires': gist.gist_expires,
3695 'created_on': gist.created_on,
3692 'created_on': gist.created_on,
3696 'modified_at': gist.modified_at,
3693 'modified_at': gist.modified_at,
3697 'content': None,
3694 'content': None,
3698 'acl_level': gist.acl_level,
3695 'acl_level': gist.acl_level,
3699 }
3696 }
3700 return data
3697 return data
3701
3698
3702 def __json__(self):
3699 def __json__(self):
3703 data = dict(
3700 data = dict(
3704 )
3701 )
3705 data.update(self.get_api_data())
3702 data.update(self.get_api_data())
3706 return data
3703 return data
3707 # SCM functions
3704 # SCM functions
3708
3705
3709 def scm_instance(self, **kwargs):
3706 def scm_instance(self, **kwargs):
3710 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3707 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3711 return get_vcs_instance(
3708 return get_vcs_instance(
3712 repo_path=safe_str(full_repo_path), create=False)
3709 repo_path=safe_str(full_repo_path), create=False)
3713
3710
3714
3711
3715 class ExternalIdentity(Base, BaseModel):
3712 class ExternalIdentity(Base, BaseModel):
3716 __tablename__ = 'external_identities'
3713 __tablename__ = 'external_identities'
3717 __table_args__ = (
3714 __table_args__ = (
3718 Index('local_user_id_idx', 'local_user_id'),
3715 Index('local_user_id_idx', 'local_user_id'),
3719 Index('external_id_idx', 'external_id'),
3716 Index('external_id_idx', 'external_id'),
3720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3717 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3721 'mysql_charset': 'utf8'})
3718 'mysql_charset': 'utf8'})
3722
3719
3723 external_id = Column('external_id', Unicode(255), default=u'',
3720 external_id = Column('external_id', Unicode(255), default=u'',
3724 primary_key=True)
3721 primary_key=True)
3725 external_username = Column('external_username', Unicode(1024), default=u'')
3722 external_username = Column('external_username', Unicode(1024), default=u'')
3726 local_user_id = Column('local_user_id', Integer(),
3723 local_user_id = Column('local_user_id', Integer(),
3727 ForeignKey('users.user_id'), primary_key=True)
3724 ForeignKey('users.user_id'), primary_key=True)
3728 provider_name = Column('provider_name', Unicode(255), default=u'',
3725 provider_name = Column('provider_name', Unicode(255), default=u'',
3729 primary_key=True)
3726 primary_key=True)
3730 access_token = Column('access_token', String(1024), default=u'')
3727 access_token = Column('access_token', String(1024), default=u'')
3731 alt_token = Column('alt_token', String(1024), default=u'')
3728 alt_token = Column('alt_token', String(1024), default=u'')
3732 token_secret = Column('token_secret', String(1024), default=u'')
3729 token_secret = Column('token_secret', String(1024), default=u'')
3733
3730
3734 @classmethod
3731 @classmethod
3735 def by_external_id_and_provider(cls, external_id, provider_name,
3732 def by_external_id_and_provider(cls, external_id, provider_name,
3736 local_user_id=None):
3733 local_user_id=None):
3737 """
3734 """
3738 Returns ExternalIdentity instance based on search params
3735 Returns ExternalIdentity instance based on search params
3739
3736
3740 :param external_id:
3737 :param external_id:
3741 :param provider_name:
3738 :param provider_name:
3742 :return: ExternalIdentity
3739 :return: ExternalIdentity
3743 """
3740 """
3744 query = cls.query()
3741 query = cls.query()
3745 query = query.filter(cls.external_id == external_id)
3742 query = query.filter(cls.external_id == external_id)
3746 query = query.filter(cls.provider_name == provider_name)
3743 query = query.filter(cls.provider_name == provider_name)
3747 if local_user_id:
3744 if local_user_id:
3748 query = query.filter(cls.local_user_id == local_user_id)
3745 query = query.filter(cls.local_user_id == local_user_id)
3749 return query.first()
3746 return query.first()
3750
3747
3751 @classmethod
3748 @classmethod
3752 def user_by_external_id_and_provider(cls, external_id, provider_name):
3749 def user_by_external_id_and_provider(cls, external_id, provider_name):
3753 """
3750 """
3754 Returns User instance based on search params
3751 Returns User instance based on search params
3755
3752
3756 :param external_id:
3753 :param external_id:
3757 :param provider_name:
3754 :param provider_name:
3758 :return: User
3755 :return: User
3759 """
3756 """
3760 query = User.query()
3757 query = User.query()
3761 query = query.filter(cls.external_id == external_id)
3758 query = query.filter(cls.external_id == external_id)
3762 query = query.filter(cls.provider_name == provider_name)
3759 query = query.filter(cls.provider_name == provider_name)
3763 query = query.filter(User.user_id == cls.local_user_id)
3760 query = query.filter(User.user_id == cls.local_user_id)
3764 return query.first()
3761 return query.first()
3765
3762
3766 @classmethod
3763 @classmethod
3767 def by_local_user_id(cls, local_user_id):
3764 def by_local_user_id(cls, local_user_id):
3768 """
3765 """
3769 Returns all tokens for user
3766 Returns all tokens for user
3770
3767
3771 :param local_user_id:
3768 :param local_user_id:
3772 :return: ExternalIdentity
3769 :return: ExternalIdentity
3773 """
3770 """
3774 query = cls.query()
3771 query = cls.query()
3775 query = query.filter(cls.local_user_id == local_user_id)
3772 query = query.filter(cls.local_user_id == local_user_id)
3776 return query
3773 return query
3777
3774
3778
3775
3779 class Integration(Base, BaseModel):
3776 class Integration(Base, BaseModel):
3780 __tablename__ = 'integrations'
3777 __tablename__ = 'integrations'
3781 __table_args__ = (
3778 __table_args__ = (
3782 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3779 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3783 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3780 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3784 )
3781 )
3785
3782
3786 integration_id = Column('integration_id', Integer(), primary_key=True)
3783 integration_id = Column('integration_id', Integer(), primary_key=True)
3787 integration_type = Column('integration_type', String(255))
3784 integration_type = Column('integration_type', String(255))
3788 enabled = Column('enabled', Boolean(), nullable=False)
3785 enabled = Column('enabled', Boolean(), nullable=False)
3789 name = Column('name', String(255), nullable=False)
3786 name = Column('name', String(255), nullable=False)
3790 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3787 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3791 default=False)
3788 default=False)
3792
3789
3793 settings = Column(
3790 settings = Column(
3794 'settings_json', MutationObj.as_mutable(
3791 'settings_json', MutationObj.as_mutable(
3795 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3792 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3796 repo_id = Column(
3793 repo_id = Column(
3797 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3794 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3798 nullable=True, unique=None, default=None)
3795 nullable=True, unique=None, default=None)
3799 repo = relationship('Repository', lazy='joined')
3796 repo = relationship('Repository', lazy='joined')
3800
3797
3801 repo_group_id = Column(
3798 repo_group_id = Column(
3802 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3799 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
3803 nullable=True, unique=None, default=None)
3800 nullable=True, unique=None, default=None)
3804 repo_group = relationship('RepoGroup', lazy='joined')
3801 repo_group = relationship('RepoGroup', lazy='joined')
3805
3802
3806 @property
3803 @property
3807 def scope(self):
3804 def scope(self):
3808 if self.repo:
3805 if self.repo:
3809 return repr(self.repo)
3806 return repr(self.repo)
3810 if self.repo_group:
3807 if self.repo_group:
3811 if self.child_repos_only:
3808 if self.child_repos_only:
3812 return repr(self.repo_group) + ' (child repos only)'
3809 return repr(self.repo_group) + ' (child repos only)'
3813 else:
3810 else:
3814 return repr(self.repo_group) + ' (recursive)'
3811 return repr(self.repo_group) + ' (recursive)'
3815 if self.child_repos_only:
3812 if self.child_repos_only:
3816 return 'root_repos'
3813 return 'root_repos'
3817 return 'global'
3814 return 'global'
3818
3815
3819 def __repr__(self):
3816 def __repr__(self):
3820 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3817 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
3821
3818
3822
3819
3823 class RepoReviewRuleUser(Base, BaseModel):
3820 class RepoReviewRuleUser(Base, BaseModel):
3824 __tablename__ = 'repo_review_rules_users'
3821 __tablename__ = 'repo_review_rules_users'
3825 __table_args__ = (
3822 __table_args__ = (
3826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3823 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3827 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3824 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3828 )
3825 )
3829 repo_review_rule_user_id = Column(
3826 repo_review_rule_user_id = Column(
3830 'repo_review_rule_user_id', Integer(), primary_key=True)
3827 'repo_review_rule_user_id', Integer(), primary_key=True)
3831 repo_review_rule_id = Column("repo_review_rule_id",
3828 repo_review_rule_id = Column("repo_review_rule_id",
3832 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3829 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3833 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3830 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'),
3834 nullable=False)
3831 nullable=False)
3835 user = relationship('User')
3832 user = relationship('User')
3836
3833
3837
3834
3838 class RepoReviewRuleUserGroup(Base, BaseModel):
3835 class RepoReviewRuleUserGroup(Base, BaseModel):
3839 __tablename__ = 'repo_review_rules_users_groups'
3836 __tablename__ = 'repo_review_rules_users_groups'
3840 __table_args__ = (
3837 __table_args__ = (
3841 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3838 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3842 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3839 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3843 )
3840 )
3844 repo_review_rule_users_group_id = Column(
3841 repo_review_rule_users_group_id = Column(
3845 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3842 'repo_review_rule_users_group_id', Integer(), primary_key=True)
3846 repo_review_rule_id = Column("repo_review_rule_id",
3843 repo_review_rule_id = Column("repo_review_rule_id",
3847 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3844 Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
3848 users_group_id = Column("users_group_id", Integer(),
3845 users_group_id = Column("users_group_id", Integer(),
3849 ForeignKey('users_groups.users_group_id'), nullable=False)
3846 ForeignKey('users_groups.users_group_id'), nullable=False)
3850 users_group = relationship('UserGroup')
3847 users_group = relationship('UserGroup')
3851
3848
3852
3849
3853 class RepoReviewRule(Base, BaseModel):
3850 class RepoReviewRule(Base, BaseModel):
3854 __tablename__ = 'repo_review_rules'
3851 __tablename__ = 'repo_review_rules'
3855 __table_args__ = (
3852 __table_args__ = (
3856 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3853 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3857 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3854 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
3858 )
3855 )
3859
3856
3860 repo_review_rule_id = Column(
3857 repo_review_rule_id = Column(
3861 'repo_review_rule_id', Integer(), primary_key=True)
3858 'repo_review_rule_id', Integer(), primary_key=True)
3862 repo_id = Column(
3859 repo_id = Column(
3863 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3860 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
3864 repo = relationship('Repository', backref='review_rules')
3861 repo = relationship('Repository', backref='review_rules')
3865
3862
3866 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3863 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3867 default=u'*') # glob
3864 default=u'*') # glob
3868 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3865 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'),
3869 default=u'*') # glob
3866 default=u'*') # glob
3870
3867
3871 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3868 use_authors_for_review = Column("use_authors_for_review", Boolean(),
3872 nullable=False, default=False)
3869 nullable=False, default=False)
3873 rule_users = relationship('RepoReviewRuleUser')
3870 rule_users = relationship('RepoReviewRuleUser')
3874 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3871 rule_user_groups = relationship('RepoReviewRuleUserGroup')
3875
3872
3876 @hybrid_property
3873 @hybrid_property
3877 def branch_pattern(self):
3874 def branch_pattern(self):
3878 return self._branch_pattern or '*'
3875 return self._branch_pattern or '*'
3879
3876
3880 def _validate_glob(self, value):
3877 def _validate_glob(self, value):
3881 re.compile('^' + glob2re(value) + '$')
3878 re.compile('^' + glob2re(value) + '$')
3882
3879
3883 @branch_pattern.setter
3880 @branch_pattern.setter
3884 def branch_pattern(self, value):
3881 def branch_pattern(self, value):
3885 self._validate_glob(value)
3882 self._validate_glob(value)
3886 self._branch_pattern = value or '*'
3883 self._branch_pattern = value or '*'
3887
3884
3888 @hybrid_property
3885 @hybrid_property
3889 def file_pattern(self):
3886 def file_pattern(self):
3890 return self._file_pattern or '*'
3887 return self._file_pattern or '*'
3891
3888
3892 @file_pattern.setter
3889 @file_pattern.setter
3893 def file_pattern(self, value):
3890 def file_pattern(self, value):
3894 self._validate_glob(value)
3891 self._validate_glob(value)
3895 self._file_pattern = value or '*'
3892 self._file_pattern = value or '*'
3896
3893
3897 def matches(self, branch, files_changed):
3894 def matches(self, branch, files_changed):
3898 """
3895 """
3899 Check if this review rule matches a branch/files in a pull request
3896 Check if this review rule matches a branch/files in a pull request
3900
3897
3901 :param branch: branch name for the commit
3898 :param branch: branch name for the commit
3902 :param files_changed: list of file paths changed in the pull request
3899 :param files_changed: list of file paths changed in the pull request
3903 """
3900 """
3904
3901
3905 branch = branch or ''
3902 branch = branch or ''
3906 files_changed = files_changed or []
3903 files_changed = files_changed or []
3907
3904
3908 branch_matches = True
3905 branch_matches = True
3909 if branch:
3906 if branch:
3910 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3907 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
3911 branch_matches = bool(branch_regex.search(branch))
3908 branch_matches = bool(branch_regex.search(branch))
3912
3909
3913 files_matches = True
3910 files_matches = True
3914 if self.file_pattern != '*':
3911 if self.file_pattern != '*':
3915 files_matches = False
3912 files_matches = False
3916 file_regex = re.compile(glob2re(self.file_pattern))
3913 file_regex = re.compile(glob2re(self.file_pattern))
3917 for filename in files_changed:
3914 for filename in files_changed:
3918 if file_regex.search(filename):
3915 if file_regex.search(filename):
3919 files_matches = True
3916 files_matches = True
3920 break
3917 break
3921
3918
3922 return branch_matches and files_matches
3919 return branch_matches and files_matches
3923
3920
3924 @property
3921 @property
3925 def review_users(self):
3922 def review_users(self):
3926 """ Returns the users which this rule applies to """
3923 """ Returns the users which this rule applies to """
3927
3924
3928 users = set()
3925 users = set()
3929 users |= set([
3926 users |= set([
3930 rule_user.user for rule_user in self.rule_users
3927 rule_user.user for rule_user in self.rule_users
3931 if rule_user.user.active])
3928 if rule_user.user.active])
3932 users |= set(
3929 users |= set(
3933 member.user
3930 member.user
3934 for rule_user_group in self.rule_user_groups
3931 for rule_user_group in self.rule_user_groups
3935 for member in rule_user_group.users_group.members
3932 for member in rule_user_group.users_group.members
3936 if member.user.active
3933 if member.user.active
3937 )
3934 )
3938 return users
3935 return users
3939
3936
3940 def __repr__(self):
3937 def __repr__(self):
3941 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3938 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
3942 self.repo_review_rule_id, self.repo)
3939 self.repo_review_rule_id, self.repo)
3943
3940
3944
3941
3945 class DbMigrateVersion(Base, BaseModel):
3942 class DbMigrateVersion(Base, BaseModel):
3946 __tablename__ = 'db_migrate_version'
3943 __tablename__ = 'db_migrate_version'
3947 __table_args__ = (
3944 __table_args__ = (
3948 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3945 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3949 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3946 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3950 )
3947 )
3951 repository_id = Column('repository_id', String(250), primary_key=True)
3948 repository_id = Column('repository_id', String(250), primary_key=True)
3952 repository_path = Column('repository_path', Text)
3949 repository_path = Column('repository_path', Text)
3953 version = Column('version', Integer)
3950 version = Column('version', Integer)
3954
3951
3955
3952
3956 class DbSession(Base, BaseModel):
3953 class DbSession(Base, BaseModel):
3957 __tablename__ = 'db_session'
3954 __tablename__ = 'db_session'
3958 __table_args__ = (
3955 __table_args__ = (
3959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3956 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3960 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3957 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3961 )
3958 )
3962
3959
3963 def __repr__(self):
3960 def __repr__(self):
3964 return '<DB:DbSession({})>'.format(self.id)
3961 return '<DB:DbSession({})>'.format(self.id)
3965
3962
3966 id = Column('id', Integer())
3963 id = Column('id', Integer())
3967 namespace = Column('namespace', String(255), primary_key=True)
3964 namespace = Column('namespace', String(255), primary_key=True)
3968 accessed = Column('accessed', DateTime, nullable=False)
3965 accessed = Column('accessed', DateTime, nullable=False)
3969 created = Column('created', DateTime, nullable=False)
3966 created = Column('created', DateTime, nullable=False)
3970 data = Column('data', PickleType, nullable=False)
3967 data = Column('data', PickleType, nullable=False)
@@ -1,158 +1,158 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <%
3 <%
4 elems = [
4 elems = [
5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
5 (_('Created on'), h.format_date(c.user.created_on), '', ''),
6 (_('Source of Record'), c.user.extern_type, '', ''),
6 (_('Source of Record'), c.user.extern_type, '', ''),
7
7
8 (_('Last login'), c.user.last_login or '-', '', ''),
8 (_('Last login'), c.user.last_login or '-', '', ''),
9 (_('Last activity'), h.format_date(h.time_to_datetime(c.user.user_data.get('last_activity', 0))), '', ''),
9 (_('Last activity'), c.user.last_activity, '', ''),
10
10
11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
11 (_('Repositories'), len(c.user.repositories), '', [x.repo_name for x in c.user.repositories]),
12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
12 (_('Repository groups'), len(c.user.repository_groups), '', [x.group_name for x in c.user.repository_groups]),
13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
13 (_('User groups'), len(c.user.user_groups), '', [x.users_group_name for x in c.user.user_groups]),
14
14
15 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
15 (_('Member of User groups'), len(c.user.group_member), '', [x.users_group.users_group_name for x in c.user.group_member]),
16 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
16 (_('Force password change'), c.user.user_data.get('force_password_change', 'False'), '', ''),
17 ]
17 ]
18 %>
18 %>
19
19
20 <div class="panel panel-default">
20 <div class="panel panel-default">
21 <div class="panel-heading">
21 <div class="panel-heading">
22 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
22 <h3 class="panel-title">${_('User: %s') % c.user.username}</h3>
23 </div>
23 </div>
24 <div class="panel-body">
24 <div class="panel-body">
25 ${base.dt_info_panel(elems)}
25 ${base.dt_info_panel(elems)}
26 </div>
26 </div>
27 </div>
27 </div>
28
28
29 <div class="panel panel-default">
29 <div class="panel panel-default">
30 <div class="panel-heading">
30 <div class="panel-heading">
31 <h3 class="panel-title">${_('Force Password Reset')}</h3>
31 <h3 class="panel-title">${_('Force Password Reset')}</h3>
32 </div>
32 </div>
33 <div class="panel-body">
33 <div class="panel-body">
34 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
34 ${h.secure_form(h.url('force_password_reset_user', user_id=c.user.user_id), method='post')}
35 <div class="field">
35 <div class="field">
36 <button class="btn btn-default" type="submit">
36 <button class="btn btn-default" type="submit">
37 <i class="icon-lock"></i>
37 <i class="icon-lock"></i>
38 %if c.user.user_data.get('force_password_change'):
38 %if c.user.user_data.get('force_password_change'):
39 ${_('Disable forced password reset')}
39 ${_('Disable forced password reset')}
40 %else:
40 %else:
41 ${_('Enable forced password reset')}
41 ${_('Enable forced password reset')}
42 %endif
42 %endif
43 </button>
43 </button>
44 </div>
44 </div>
45 <div class="field">
45 <div class="field">
46 <span class="help-block">
46 <span class="help-block">
47 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
47 ${_("When this is enabled user will have to change they password when they next use RhodeCode system. This will also forbid vcs operations until someone makes a password change in the web interface")}
48 </span>
48 </span>
49 </div>
49 </div>
50 ${h.end_form()}
50 ${h.end_form()}
51 </div>
51 </div>
52 </div>
52 </div>
53
53
54 <div class="panel panel-default">
54 <div class="panel panel-default">
55 <div class="panel-heading">
55 <div class="panel-heading">
56 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
56 <h3 class="panel-title">${_('Personal Repository Group')}</h3>
57 </div>
57 </div>
58 <div class="panel-body">
58 <div class="panel-body">
59 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
59 ${h.secure_form(h.url('create_personal_repo_group', user_id=c.user.user_id), method='post')}
60
60
61 %if c.personal_repo_group:
61 %if c.personal_repo_group:
62 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, url('repo_group_home', group_name=c.personal_repo_group.group_name))}</div>
62 <div class="panel-body-title-text">${_('Users personal repository group')} : ${h.link_to(c.personal_repo_group.group_name, url('repo_group_home', group_name=c.personal_repo_group.group_name))}</div>
63 %else:
63 %else:
64 <div class="panel-body-title-text">
64 <div class="panel-body-title-text">
65 ${_('This user currently does not have a personal repository group')}
65 ${_('This user currently does not have a personal repository group')}
66 <br/>
66 <br/>
67 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
67 ${_('New group will be created at: `/%(path)s`') % {'path': c.personal_repo_group_name}}
68 </div>
68 </div>
69 %endif
69 %endif
70 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
70 <button class="btn btn-default" type="submit" ${'disabled="disabled"' if c.personal_repo_group else ''}>
71 <i class="icon-folder-close"></i>
71 <i class="icon-folder-close"></i>
72 ${_('Create personal repository group')}
72 ${_('Create personal repository group')}
73 </button>
73 </button>
74 ${h.end_form()}
74 ${h.end_form()}
75 </div>
75 </div>
76 </div>
76 </div>
77
77
78
78
79 <div class="panel panel-danger">
79 <div class="panel panel-danger">
80 <div class="panel-heading">
80 <div class="panel-heading">
81 <h3 class="panel-title">${_('Delete User')}</h3>
81 <h3 class="panel-title">${_('Delete User')}</h3>
82 </div>
82 </div>
83 <div class="panel-body">
83 <div class="panel-body">
84 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
84 ${h.secure_form(h.url('delete_user', user_id=c.user.user_id), method='delete')}
85
85
86 <table class="display">
86 <table class="display">
87 <tr>
87 <tr>
88 <td>
88 <td>
89 ${ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
89 ${ungettext('This user owns %s repository.', 'This user owns %s repositories.', len(c.user.repositories)) % len(c.user.repositories)}
90 </td>
90 </td>
91 <td>
91 <td>
92 %if len(c.user.repositories) > 0:
92 %if len(c.user.repositories) > 0:
93 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
93 <input type="radio" id="user_repos_1" name="user_repos" value="detach" checked="checked"/> <label for="user_repos_1">${_('Detach repositories')}</label>
94 %endif
94 %endif
95 </td>
95 </td>
96 <td>
96 <td>
97 %if len(c.user.repositories) > 0:
97 %if len(c.user.repositories) > 0:
98 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
98 <input type="radio" id="user_repos_2" name="user_repos" value="delete" /> <label for="user_repos_2">${_('Delete repositories')}</label>
99 %endif
99 %endif
100 </td>
100 </td>
101 </tr>
101 </tr>
102
102
103 <tr>
103 <tr>
104 <td>
104 <td>
105 ${ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
105 ${ungettext('This user owns %s repository group.', 'This user owns %s repository groups.', len(c.user.repository_groups)) % len(c.user.repository_groups)}
106 </td>
106 </td>
107 <td>
107 <td>
108 %if len(c.user.repository_groups) > 0:
108 %if len(c.user.repository_groups) > 0:
109 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
109 <input type="radio" id="user_repo_groups_1" name="user_repo_groups" value="detach" checked="checked"/> <label for="user_repo_groups_1">${_('Detach repository groups')}</label>
110 %endif
110 %endif
111 </td>
111 </td>
112 <td>
112 <td>
113 %if len(c.user.repository_groups) > 0:
113 %if len(c.user.repository_groups) > 0:
114 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
114 <input type="radio" id="user_repo_groups_2" name="user_repo_groups" value="delete" /> <label for="user_repo_groups_2">${_('Delete repositories')}</label>
115 %endif
115 %endif
116 </td>
116 </td>
117 </tr>
117 </tr>
118
118
119 <tr>
119 <tr>
120 <td>
120 <td>
121 ${ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
121 ${ungettext('This user owns %s user group.', 'This user owns %s user groups.', len(c.user.user_groups)) % len(c.user.user_groups)}
122 </td>
122 </td>
123 <td>
123 <td>
124 %if len(c.user.user_groups) > 0:
124 %if len(c.user.user_groups) > 0:
125 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
125 <input type="radio" id="user_user_groups_1" name="user_user_groups" value="detach" checked="checked"/> <label for="user_user_groups_1">${_('Detach user groups')}</label>
126 %endif
126 %endif
127 </td>
127 </td>
128 <td>
128 <td>
129 %if len(c.user.user_groups) > 0:
129 %if len(c.user.user_groups) > 0:
130 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
130 <input type="radio" id="user_user_groups_2" name="user_user_groups" value="delete" /> <label for="user_user_groups_2">${_('Delete repositories')}</label>
131 %endif
131 %endif
132 </td>
132 </td>
133 </tr>
133 </tr>
134 </table>
134 </table>
135 <div style="margin: 0 0 20px 0" class="fake-space"></div>
135 <div style="margin: 0 0 20px 0" class="fake-space"></div>
136
136
137 <div class="field">
137 <div class="field">
138 <button class="btn btn-small btn-danger" type="submit"
138 <button class="btn btn-small btn-danger" type="submit"
139 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
139 onclick="return confirm('${_('Confirm to delete this user: %s') % c.user.username}');"
140 ${"disabled" if not c.can_delete_user else ""}>
140 ${"disabled" if not c.can_delete_user else ""}>
141 ${_('Delete this user')}
141 ${_('Delete this user')}
142 </button>
142 </button>
143 </div>
143 </div>
144 % if c.can_delete_user_message:
144 % if c.can_delete_user_message:
145 <p class="help-block">${c.can_delete_user_message}</p>
145 <p class="help-block">${c.can_delete_user_message}</p>
146 % endif
146 % endif
147
147
148 <div class="field">
148 <div class="field">
149 <span class="help-block">
149 <span class="help-block">
150 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
150 %if len(c.user.repositories) > 0 or len(c.user.repository_groups) > 0 or len(c.user.user_groups) > 0:
151 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
151 <p class="help-block">${_("When selecting the detach option, the depending objects owned by this user will be assigned to the `%s` super admin in the system. The delete option will delete the user's repositories!") % (c.first_admin.full_name)}</p>
152 %endif
152 %endif
153 </span>
153 </span>
154 </div>
154 </div>
155
155
156 ${h.end_form()}
156 ${h.end_form()}
157 </div>
157 </div>
158 </div>
158 </div>
General Comments 0
You need to be logged in to leave comments. Login now