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