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