##// END OF EJS Templates
notification to commit author + gardening
marcink -
r1716:7d1fc253 beta
parent child Browse files
Show More
@@ -1,367 +1,367 b''
1 import traceback
1 import traceback
2 import logging
2 import logging
3
3
4 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
4 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
5 from rhodecode.lib.auth import HasPermissionAllDecorator, \
5 from rhodecode.lib.auth import HasPermissionAllDecorator, \
6 HasPermissionAnyDecorator
6 HasPermissionAnyDecorator
7 from rhodecode.model.scm import ScmModel
7 from rhodecode.model.scm import ScmModel
8
8
9 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
9 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
10 from rhodecode.model.repo import RepoModel
10 from rhodecode.model.repo import RepoModel
11 from rhodecode.model.user import UserModel
11 from rhodecode.model.user import UserModel
12 from rhodecode.model.repo_permission import RepositoryPermissionModel
12 from rhodecode.model.repo_permission import RepositoryPermissionModel
13 from rhodecode.model.users_group import UsersGroupModel
13 from rhodecode.model.users_group import UsersGroupModel
14 from rhodecode.model import users_group
14 from rhodecode.model import users_group
15 from rhodecode.model.repos_group import ReposGroupModel
15 from rhodecode.model.repos_group import ReposGroupModel
16 from sqlalchemy.orm.exc import NoResultFound
16 from sqlalchemy.orm.exc import NoResultFound
17
17
18 log = logging.getLogger(__name__)
18 log = logging.getLogger(__name__)
19
19
20
20
21 class ApiController(JSONRPCController):
21 class ApiController(JSONRPCController):
22 """
22 """
23 API Controller
23 API Controller
24
24
25
25
26 Each method needs to have USER as argument this is then based on given
26 Each method needs to have USER as argument this is then based on given
27 API_KEY propagated as instance of user object
27 API_KEY propagated as instance of user object
28
28
29 Preferably this should be first argument also
29 Preferably this should be first argument also
30
30
31
31
32 Each function should also **raise** JSONRPCError for any
32 Each function should also **raise** JSONRPCError for any
33 errors that happens
33 errors that happens
34
34
35 """
35 """
36
36
37 @HasPermissionAllDecorator('hg.admin')
37 @HasPermissionAllDecorator('hg.admin')
38 def pull(self, apiuser, repo):
38 def pull(self, apiuser, repo):
39 """
39 """
40 Dispatch pull action on given repo
40 Dispatch pull action on given repo
41
41
42
42
43 :param user:
43 :param user:
44 :param repo:
44 :param repo:
45 """
45 """
46
46
47 if Repository.is_valid(repo) is False:
47 if Repository.is_valid(repo) is False:
48 raise JSONRPCError('Unknown repo "%s"' % repo)
48 raise JSONRPCError('Unknown repo "%s"' % repo)
49
49
50 try:
50 try:
51 ScmModel().pull_changes(repo, self.rhodecode_user.username)
51 ScmModel().pull_changes(repo, self.rhodecode_user.username)
52 return 'Pulled from %s' % repo
52 return 'Pulled from %s' % repo
53 except Exception:
53 except Exception:
54 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
54 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
55
55
56 @HasPermissionAllDecorator('hg.admin')
56 @HasPermissionAllDecorator('hg.admin')
57 def get_user(self, apiuser, username):
57 def get_user(self, apiuser, username):
58 """"
58 """"
59 Get a user by username
59 Get a user by username
60
60
61 :param apiuser
61 :param apiuser
62 :param username
62 :param username
63 """
63 """
64
64
65 user = User.get_by_username(username)
65 user = User.get_by_username(username)
66 if not user:
66 if not user:
67 return None
67 return None
68
68
69 return dict(id=user.user_id,
69 return dict(id=user.user_id,
70 username=user.username,
70 username=user.username,
71 firstname=user.name,
71 firstname=user.name,
72 lastname=user.lastname,
72 lastname=user.lastname,
73 email=user.email,
73 email=user.email,
74 active=user.active,
74 active=user.active,
75 admin=user.admin,
75 admin=user.admin,
76 ldap=user.ldap_dn)
76 ldap=user.ldap_dn)
77
77
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 def get_users(self, apiuser):
79 def get_users(self, apiuser):
80 """"
80 """"
81 Get all users
81 Get all users
82
82
83 :param apiuser
83 :param apiuser
84 """
84 """
85
85
86 result = []
86 result = []
87 for user in User.getAll():
87 for user in User.getAll():
88 result.append(dict(id=user.user_id,
88 result.append(dict(id=user.user_id,
89 username=user.username,
89 username=user.username,
90 firstname=user.name,
90 firstname=user.name,
91 lastname=user.lastname,
91 lastname=user.lastname,
92 email=user.email,
92 email=user.email,
93 active=user.active,
93 active=user.active,
94 admin=user.admin,
94 admin=user.admin,
95 ldap=user.ldap_dn))
95 ldap=user.ldap_dn))
96 return result
96 return result
97
97
98 @HasPermissionAllDecorator('hg.admin')
98 @HasPermissionAllDecorator('hg.admin')
99 def create_user(self, apiuser, username, password, firstname,
99 def create_user(self, apiuser, username, password, firstname,
100 lastname, email, active=True, admin=False, ldap_dn=None):
100 lastname, email, active=True, admin=False, ldap_dn=None):
101 """
101 """
102 Create new user
102 Create new user
103
103
104 :param apiuser:
104 :param apiuser:
105 :param username:
105 :param username:
106 :param password:
106 :param password:
107 :param name:
107 :param name:
108 :param lastname:
108 :param lastname:
109 :param email:
109 :param email:
110 :param active:
110 :param active:
111 :param admin:
111 :param admin:
112 :param ldap_dn:
112 :param ldap_dn:
113 """
113 """
114
114
115 if self.get_user(apiuser, username):
115 if User.get_by_username(username):
116 raise JSONRPCError("user %s already exist" % username)
116 raise JSONRPCError("user %s already exist" % username)
117
117
118 try:
118 try:
119 UserModel().create_or_update(username, password, email, firstname,
119 UserModel().create_or_update(username, password, email, firstname,
120 lastname, active, admin, ldap_dn)
120 lastname, active, admin, ldap_dn)
121 return dict(msg='created new user %s' % username)
121 return dict(msg='created new user %s' % username)
122 except Exception:
122 except Exception:
123 log.error(traceback.format_exc())
123 log.error(traceback.format_exc())
124 raise JSONRPCError('failed to create user %s' % username)
124 raise JSONRPCError('failed to create user %s' % username)
125
125
126 @HasPermissionAllDecorator('hg.admin')
126 @HasPermissionAllDecorator('hg.admin')
127 def get_users_group(self, apiuser, group_name):
127 def get_users_group(self, apiuser, group_name):
128 """"
128 """"
129 Get users group by name
129 Get users group by name
130
130
131 :param apiuser
131 :param apiuser
132 :param group_name
132 :param group_name
133 """
133 """
134
134
135 users_group = UsersGroup.get_by_group_name(group_name)
135 users_group = UsersGroup.get_by_group_name(group_name)
136 if not users_group:
136 if not users_group:
137 return None
137 return None
138
138
139 members = []
139 members = []
140 for user in users_group.members:
140 for user in users_group.members:
141 user = user.user
141 user = user.user
142 members.append(dict(id=user.user_id,
142 members.append(dict(id=user.user_id,
143 username=user.username,
143 username=user.username,
144 firstname=user.name,
144 firstname=user.name,
145 lastname=user.lastname,
145 lastname=user.lastname,
146 email=user.email,
146 email=user.email,
147 active=user.active,
147 active=user.active,
148 admin=user.admin,
148 admin=user.admin,
149 ldap=user.ldap_dn))
149 ldap=user.ldap_dn))
150
150
151 return dict(id=users_group.users_group_id,
151 return dict(id=users_group.users_group_id,
152 name=users_group.users_group_name,
152 name=users_group.users_group_name,
153 active=users_group.users_group_active,
153 active=users_group.users_group_active,
154 members=members)
154 members=members)
155
155
156 @HasPermissionAllDecorator('hg.admin')
156 @HasPermissionAllDecorator('hg.admin')
157 def get_users_groups(self, apiuser):
157 def get_users_groups(self, apiuser):
158 """"
158 """"
159 Get all users groups
159 Get all users groups
160
160
161 :param apiuser
161 :param apiuser
162 """
162 """
163
163
164 result = []
164 result = []
165 for users_group in UsersGroup.getAll():
165 for users_group in UsersGroup.getAll():
166 members = []
166 members = []
167 for user in users_group.members:
167 for user in users_group.members:
168 user = user.user
168 user = user.user
169 members.append(dict(id=user.user_id,
169 members.append(dict(id=user.user_id,
170 username=user.username,
170 username=user.username,
171 firstname=user.name,
171 firstname=user.name,
172 lastname=user.lastname,
172 lastname=user.lastname,
173 email=user.email,
173 email=user.email,
174 active=user.active,
174 active=user.active,
175 admin=user.admin,
175 admin=user.admin,
176 ldap=user.ldap_dn))
176 ldap=user.ldap_dn))
177
177
178 result.append(dict(id=users_group.users_group_id,
178 result.append(dict(id=users_group.users_group_id,
179 name=users_group.users_group_name,
179 name=users_group.users_group_name,
180 active=users_group.users_group_active,
180 active=users_group.users_group_active,
181 members=members))
181 members=members))
182 return result
182 return result
183
183
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 def create_users_group(self, apiuser, name, active=True):
185 def create_users_group(self, apiuser, name, active=True):
186 """
186 """
187 Creates an new usergroup
187 Creates an new usergroup
188
188
189 :param name:
189 :param name:
190 :param active:
190 :param active:
191 """
191 """
192
192
193 if self.get_users_group(apiuser, name):
193 if self.get_users_group(apiuser, name):
194 raise JSONRPCError("users group %s already exist" % name)
194 raise JSONRPCError("users group %s already exist" % name)
195
195
196 try:
196 try:
197 form_data = dict(users_group_name=name,
197 form_data = dict(users_group_name=name,
198 users_group_active=active)
198 users_group_active=active)
199 ug = UsersGroup.create(form_data)
199 ug = UsersGroup.create(form_data)
200 return dict(id=ug.users_group_id,
200 return dict(id=ug.users_group_id,
201 msg='created new users group %s' % name)
201 msg='created new users group %s' % name)
202 except Exception:
202 except Exception:
203 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
204 raise JSONRPCError('failed to create group %s' % name)
204 raise JSONRPCError('failed to create group %s' % name)
205
205
206 @HasPermissionAllDecorator('hg.admin')
206 @HasPermissionAllDecorator('hg.admin')
207 def add_user_to_users_group(self, apiuser, group_name, user_name):
207 def add_user_to_users_group(self, apiuser, group_name, user_name):
208 """"
208 """"
209 Add a user to a group
209 Add a user to a group
210
210
211 :param apiuser
211 :param apiuser
212 :param group_name
212 :param group_name
213 :param user_name
213 :param user_name
214 """
214 """
215
215
216 try:
216 try:
217 users_group = UsersGroup.get_by_group_name(group_name)
217 users_group = UsersGroup.get_by_group_name(group_name)
218 if not users_group:
218 if not users_group:
219 raise JSONRPCError('unknown users group %s' % group_name)
219 raise JSONRPCError('unknown users group %s' % group_name)
220
220
221 try:
221 try:
222 user = User.get_by_username(user_name)
222 user = User.get_by_username(user_name)
223 except NoResultFound:
223 except NoResultFound:
224 raise JSONRPCError('unknown user %s' % user_name)
224 raise JSONRPCError('unknown user %s' % user_name)
225
225
226 ugm = UsersGroupModel().add_user_to_group(users_group, user)
226 ugm = UsersGroupModel().add_user_to_group(users_group, user)
227
227
228 return dict(id=ugm.users_group_member_id,
228 return dict(id=ugm.users_group_member_id,
229 msg='created new users group member')
229 msg='created new users group member')
230 except Exception:
230 except Exception:
231 log.error(traceback.format_exc())
231 log.error(traceback.format_exc())
232 raise JSONRPCError('failed to create users group member')
232 raise JSONRPCError('failed to create users group member')
233
233
234 @HasPermissionAnyDecorator('hg.admin')
234 @HasPermissionAnyDecorator('hg.admin')
235 def get_repo(self, apiuser, repo_name):
235 def get_repo(self, apiuser, repo_name):
236 """"
236 """"
237 Get repository by name
237 Get repository by name
238
238
239 :param apiuser
239 :param apiuser
240 :param repo_name
240 :param repo_name
241 """
241 """
242
242
243 try:
243 try:
244 repo = Repository.get_by_repo_name(repo_name)
244 repo = Repository.get_by_repo_name(repo_name)
245 except NoResultFound:
245 except NoResultFound:
246 return None
246 return None
247
247
248 members = []
248 members = []
249 for user in repo.repo_to_perm:
249 for user in repo.repo_to_perm:
250 perm = user.permission.permission_name
250 perm = user.permission.permission_name
251 user = user.user
251 user = user.user
252 members.append(dict(type_="user",
252 members.append(dict(type_="user",
253 id=user.user_id,
253 id=user.user_id,
254 username=user.username,
254 username=user.username,
255 firstname=user.name,
255 firstname=user.name,
256 lastname=user.lastname,
256 lastname=user.lastname,
257 email=user.email,
257 email=user.email,
258 active=user.active,
258 active=user.active,
259 admin=user.admin,
259 admin=user.admin,
260 ldap=user.ldap_dn,
260 ldap=user.ldap_dn,
261 permission=perm))
261 permission=perm))
262 for users_group in repo.users_group_to_perm:
262 for users_group in repo.users_group_to_perm:
263 perm = users_group.permission.permission_name
263 perm = users_group.permission.permission_name
264 users_group = users_group.users_group
264 users_group = users_group.users_group
265 members.append(dict(type_="users_group",
265 members.append(dict(type_="users_group",
266 id=users_group.users_group_id,
266 id=users_group.users_group_id,
267 name=users_group.users_group_name,
267 name=users_group.users_group_name,
268 active=users_group.users_group_active,
268 active=users_group.users_group_active,
269 permission=perm))
269 permission=perm))
270
270
271 return dict(id=repo.repo_id,
271 return dict(id=repo.repo_id,
272 name=repo.repo_name,
272 name=repo.repo_name,
273 type=repo.repo_type,
273 type=repo.repo_type,
274 description=repo.description,
274 description=repo.description,
275 members=members)
275 members=members)
276
276
277 @HasPermissionAnyDecorator('hg.admin')
277 @HasPermissionAnyDecorator('hg.admin')
278 def get_repos(self, apiuser):
278 def get_repos(self, apiuser):
279 """"
279 """"
280 Get all repositories
280 Get all repositories
281
281
282 :param apiuser
282 :param apiuser
283 """
283 """
284
284
285 result = []
285 result = []
286 for repository in Repository.getAll():
286 for repository in Repository.getAll():
287 result.append(dict(id=repository.repo_id,
287 result.append(dict(id=repository.repo_id,
288 name=repository.repo_name,
288 name=repository.repo_name,
289 type=repository.repo_type,
289 type=repository.repo_type,
290 description=repository.description))
290 description=repository.description))
291 return result
291 return result
292
292
293 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
293 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
294 def create_repo(self, apiuser, name, owner_name, description='',
294 def create_repo(self, apiuser, name, owner_name, description='',
295 repo_type='hg', private=False):
295 repo_type='hg', private=False):
296 """
296 """
297 Create a repository
297 Create a repository
298
298
299 :param apiuser
299 :param apiuser
300 :param name
300 :param name
301 :param description
301 :param description
302 :param type
302 :param type
303 :param private
303 :param private
304 :param owner_name
304 :param owner_name
305 """
305 """
306
306
307 try:
307 try:
308 try:
308 try:
309 owner = User.get_by_username(owner_name)
309 owner = User.get_by_username(owner_name)
310 except NoResultFound:
310 except NoResultFound:
311 raise JSONRPCError('unknown user %s' % owner)
311 raise JSONRPCError('unknown user %s' % owner)
312
312
313 if self.get_repo(apiuser, name):
313 if self.get_repo(apiuser, name):
314 raise JSONRPCError("repo %s already exist" % name)
314 raise JSONRPCError("repo %s already exist" % name)
315
315
316 groups = name.split('/')
316 groups = name.split('/')
317 real_name = groups[-1]
317 real_name = groups[-1]
318 groups = groups[:-1]
318 groups = groups[:-1]
319 parent_id = None
319 parent_id = None
320 for g in groups:
320 for g in groups:
321 group = RepoGroup.get_by_group_name(g)
321 group = RepoGroup.get_by_group_name(g)
322 if not group:
322 if not group:
323 group = ReposGroupModel().create(dict(group_name=g,
323 group = ReposGroupModel().create(dict(group_name=g,
324 group_description='',
324 group_description='',
325 group_parent_id=parent_id))
325 group_parent_id=parent_id))
326 parent_id = group.group_id
326 parent_id = group.group_id
327
327
328 RepoModel().create(dict(repo_name=real_name,
328 RepoModel().create(dict(repo_name=real_name,
329 repo_name_full=name,
329 repo_name_full=name,
330 description=description,
330 description=description,
331 private=private,
331 private=private,
332 repo_type=repo_type,
332 repo_type=repo_type,
333 repo_group=parent_id,
333 repo_group=parent_id,
334 clone_uri=None), owner)
334 clone_uri=None), owner)
335 except Exception:
335 except Exception:
336 log.error(traceback.format_exc())
336 log.error(traceback.format_exc())
337 raise JSONRPCError('failed to create repository %s' % name)
337 raise JSONRPCError('failed to create repository %s' % name)
338
338
339 @HasPermissionAnyDecorator('hg.admin')
339 @HasPermissionAnyDecorator('hg.admin')
340 def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
340 def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
341 """
341 """
342 Add permission for a user to a repository
342 Add permission for a user to a repository
343
343
344 :param apiuser
344 :param apiuser
345 :param repo_name
345 :param repo_name
346 :param user_name
346 :param user_name
347 :param perm
347 :param perm
348 """
348 """
349
349
350 try:
350 try:
351 try:
351 try:
352 repo = Repository.get_by_repo_name(repo_name)
352 repo = Repository.get_by_repo_name(repo_name)
353 except NoResultFound:
353 except NoResultFound:
354 raise JSONRPCError('unknown repository %s' % repo)
354 raise JSONRPCError('unknown repository %s' % repo)
355
355
356 try:
356 try:
357 user = User.get_by_username(user_name)
357 user = User.get_by_username(user_name)
358 except NoResultFound:
358 except NoResultFound:
359 raise JSONRPCError('unknown user %s' % user)
359 raise JSONRPCError('unknown user %s' % user)
360
360
361 RepositoryPermissionModel()\
361 RepositoryPermissionModel()\
362 .update_or_delete_user_permission(repo, user, perm)
362 .update_or_delete_user_permission(repo, user, perm)
363 except Exception:
363 except Exception:
364 log.error(traceback.format_exc())
364 log.error(traceback.format_exc())
365 raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
365 raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
366 % dict(user=user_name, repo=repo_name))
366 % dict(user=user_name, repo=repo_name))
367
367
@@ -1,297 +1,297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import tmpl_context as c, url, request, response
29 from pylons import tmpl_context as c, url, request, response
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.decorators import jsonify
32 from pylons.decorators import jsonify
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.utils import EmptyChangeset
37 from rhodecode.lib.utils import EmptyChangeset
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.model.db import ChangesetComment
39 from rhodecode.model.db import ChangesetComment
40 from rhodecode.model.comment import ChangesetCommentsModel
40 from rhodecode.model.comment import ChangesetCommentsModel
41
41
42 from vcs.exceptions import RepositoryError, ChangesetError, \
42 from vcs.exceptions import RepositoryError, ChangesetError, \
43 ChangesetDoesNotExistError
43 ChangesetDoesNotExistError
44 from vcs.nodes import FileNode
44 from vcs.nodes import FileNode
45 from vcs.utils import diffs as differ
45 from vcs.utils import diffs as differ
46 from webob.exc import HTTPForbidden
46 from webob.exc import HTTPForbidden
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class ChangesetController(BaseRepoController):
52 class ChangesetController(BaseRepoController):
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
56 'repository.admin')
56 'repository.admin')
57 def __before__(self):
57 def __before__(self):
58 super(ChangesetController, self).__before__()
58 super(ChangesetController, self).__before__()
59 c.affected_files_cut_off = 60
59 c.affected_files_cut_off = 60
60
60
61 def index(self, revision):
61 def index(self, revision):
62
62
63 def wrap_to_table(str):
63 def wrap_to_table(str):
64
64
65 return '''<table class="code-difftable">
65 return '''<table class="code-difftable">
66 <tr class="line">
66 <tr class="line">
67 <td class="lineno new"></td>
67 <td class="lineno new"></td>
68 <td class="code"><pre>%s</pre></td>
68 <td class="code"><pre>%s</pre></td>
69 </tr>
69 </tr>
70 </table>''' % str
70 </table>''' % str
71
71
72 #get ranges of revisions if preset
72 #get ranges of revisions if preset
73 rev_range = revision.split('...')[:2]
73 rev_range = revision.split('...')[:2]
74
74
75 try:
75 try:
76 if len(rev_range) == 2:
76 if len(rev_range) == 2:
77 rev_start = rev_range[0]
77 rev_start = rev_range[0]
78 rev_end = rev_range[1]
78 rev_end = rev_range[1]
79 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
79 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
80 end=rev_end)
80 end=rev_end)
81 else:
81 else:
82 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
82 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
83
83
84 c.cs_ranges = list(rev_ranges)
84 c.cs_ranges = list(rev_ranges)
85 if not c.cs_ranges:
85 if not c.cs_ranges:
86 raise RepositoryError('Changeset range returned empty result')
86 raise RepositoryError('Changeset range returned empty result')
87
87
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
89 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
90 h.flash(str(e), category='warning')
90 h.flash(str(e), category='warning')
91 return redirect(url('home'))
91 return redirect(url('home'))
92
92
93 c.changes = OrderedDict()
93 c.changes = OrderedDict()
94 c.sum_added = 0
94 c.sum_added = 0
95 c.sum_removed = 0
95 c.sum_removed = 0
96 c.lines_added = 0
96 c.lines_added = 0
97 c.lines_deleted = 0
97 c.lines_deleted = 0
98 c.cut_off = False # defines if cut off limit is reached
98 c.cut_off = False # defines if cut off limit is reached
99
99
100 c.comments = []
100 c.comments = []
101 c.inline_comments = []
101 c.inline_comments = []
102 c.inline_cnt = 0
102 c.inline_cnt = 0
103 # Iterate over ranges (default changeset view is always one changeset)
103 # Iterate over ranges (default changeset view is always one changeset)
104 for changeset in c.cs_ranges:
104 for changeset in c.cs_ranges:
105 c.comments.extend(ChangesetCommentsModel()\
105 c.comments.extend(ChangesetCommentsModel()\
106 .get_comments(c.rhodecode_db_repo.repo_id,
106 .get_comments(c.rhodecode_db_repo.repo_id,
107 changeset.raw_id))
107 changeset.raw_id))
108 inlines = ChangesetCommentsModel()\
108 inlines = ChangesetCommentsModel()\
109 .get_inline_comments(c.rhodecode_db_repo.repo_id,
109 .get_inline_comments(c.rhodecode_db_repo.repo_id,
110 changeset.raw_id)
110 changeset.raw_id)
111 c.inline_comments.extend(inlines)
111 c.inline_comments.extend(inlines)
112 c.changes[changeset.raw_id] = []
112 c.changes[changeset.raw_id] = []
113 try:
113 try:
114 changeset_parent = changeset.parents[0]
114 changeset_parent = changeset.parents[0]
115 except IndexError:
115 except IndexError:
116 changeset_parent = None
116 changeset_parent = None
117
117
118 #==================================================================
118 #==================================================================
119 # ADDED FILES
119 # ADDED FILES
120 #==================================================================
120 #==================================================================
121 for node in changeset.added:
121 for node in changeset.added:
122
122
123 filenode_old = FileNode(node.path, '', EmptyChangeset())
123 filenode_old = FileNode(node.path, '', EmptyChangeset())
124 if filenode_old.is_binary or node.is_binary:
124 if filenode_old.is_binary or node.is_binary:
125 diff = wrap_to_table(_('binary file'))
125 diff = wrap_to_table(_('binary file'))
126 st = (0, 0)
126 st = (0, 0)
127 else:
127 else:
128 # in this case node.size is good parameter since those are
128 # in this case node.size is good parameter since those are
129 # added nodes and their size defines how many changes were
129 # added nodes and their size defines how many changes were
130 # made
130 # made
131 c.sum_added += node.size
131 c.sum_added += node.size
132 if c.sum_added < self.cut_off_limit:
132 if c.sum_added < self.cut_off_limit:
133 f_gitdiff = differ.get_gitdiff(filenode_old, node)
133 f_gitdiff = differ.get_gitdiff(filenode_old, node)
134 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
134 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
135
135
136 st = d.stat()
136 st = d.stat()
137 diff = d.as_html()
137 diff = d.as_html()
138
138
139 else:
139 else:
140 diff = wrap_to_table(_('Changeset is to big and '
140 diff = wrap_to_table(_('Changeset is to big and '
141 'was cut off, see raw '
141 'was cut off, see raw '
142 'changeset instead'))
142 'changeset instead'))
143 c.cut_off = True
143 c.cut_off = True
144 break
144 break
145
145
146 cs1 = None
146 cs1 = None
147 cs2 = node.last_changeset.raw_id
147 cs2 = node.last_changeset.raw_id
148 c.lines_added += st[0]
148 c.lines_added += st[0]
149 c.lines_deleted += st[1]
149 c.lines_deleted += st[1]
150 c.changes[changeset.raw_id].append(('added', node, diff,
150 c.changes[changeset.raw_id].append(('added', node, diff,
151 cs1, cs2, st))
151 cs1, cs2, st))
152
152
153 #==================================================================
153 #==================================================================
154 # CHANGED FILES
154 # CHANGED FILES
155 #==================================================================
155 #==================================================================
156 if not c.cut_off:
156 if not c.cut_off:
157 for node in changeset.changed:
157 for node in changeset.changed:
158 try:
158 try:
159 filenode_old = changeset_parent.get_node(node.path)
159 filenode_old = changeset_parent.get_node(node.path)
160 except ChangesetError:
160 except ChangesetError:
161 log.warning('Unable to fetch parent node for diff')
161 log.warning('Unable to fetch parent node for diff')
162 filenode_old = FileNode(node.path, '',
162 filenode_old = FileNode(node.path, '',
163 EmptyChangeset())
163 EmptyChangeset())
164
164
165 if filenode_old.is_binary or node.is_binary:
165 if filenode_old.is_binary or node.is_binary:
166 diff = wrap_to_table(_('binary file'))
166 diff = wrap_to_table(_('binary file'))
167 st = (0, 0)
167 st = (0, 0)
168 else:
168 else:
169
169
170 if c.sum_removed < self.cut_off_limit:
170 if c.sum_removed < self.cut_off_limit:
171 f_gitdiff = differ.get_gitdiff(filenode_old, node)
171 f_gitdiff = differ.get_gitdiff(filenode_old, node)
172 d = differ.DiffProcessor(f_gitdiff,
172 d = differ.DiffProcessor(f_gitdiff,
173 format='gitdiff')
173 format='gitdiff')
174 st = d.stat()
174 st = d.stat()
175 if (st[0] + st[1]) * 256 > self.cut_off_limit:
175 if (st[0] + st[1]) * 256 > self.cut_off_limit:
176 diff = wrap_to_table(_('Diff is to big '
176 diff = wrap_to_table(_('Diff is to big '
177 'and was cut off, see '
177 'and was cut off, see '
178 'raw diff instead'))
178 'raw diff instead'))
179 else:
179 else:
180 diff = d.as_html()
180 diff = d.as_html()
181
181
182 if diff:
182 if diff:
183 c.sum_removed += len(diff)
183 c.sum_removed += len(diff)
184 else:
184 else:
185 diff = wrap_to_table(_('Changeset is to big and '
185 diff = wrap_to_table(_('Changeset is to big and '
186 'was cut off, see raw '
186 'was cut off, see raw '
187 'changeset instead'))
187 'changeset instead'))
188 c.cut_off = True
188 c.cut_off = True
189 break
189 break
190
190
191 cs1 = filenode_old.last_changeset.raw_id
191 cs1 = filenode_old.last_changeset.raw_id
192 cs2 = node.last_changeset.raw_id
192 cs2 = node.last_changeset.raw_id
193 c.lines_added += st[0]
193 c.lines_added += st[0]
194 c.lines_deleted += st[1]
194 c.lines_deleted += st[1]
195 c.changes[changeset.raw_id].append(('changed', node, diff,
195 c.changes[changeset.raw_id].append(('changed', node, diff,
196 cs1, cs2, st))
196 cs1, cs2, st))
197
197
198 #==================================================================
198 #==================================================================
199 # REMOVED FILES
199 # REMOVED FILES
200 #==================================================================
200 #==================================================================
201 if not c.cut_off:
201 if not c.cut_off:
202 for node in changeset.removed:
202 for node in changeset.removed:
203 c.changes[changeset.raw_id].append(('removed', node, None,
203 c.changes[changeset.raw_id].append(('removed', node, None,
204 None, None, (0, 0)))
204 None, None, (0, 0)))
205
205
206 # count inline comments
206 # count inline comments
207 for path, lines in c.inline_comments:
207 for path, lines in c.inline_comments:
208 for comments in lines.values():
208 for comments in lines.values():
209 c.inline_cnt += len(comments)
209 c.inline_cnt += len(comments)
210
210
211 if len(c.cs_ranges) == 1:
211 if len(c.cs_ranges) == 1:
212 c.changeset = c.cs_ranges[0]
212 c.changeset = c.cs_ranges[0]
213 c.changes = c.changes[c.changeset.raw_id]
213 c.changes = c.changes[c.changeset.raw_id]
214
214
215 return render('changeset/changeset.html')
215 return render('changeset/changeset.html')
216 else:
216 else:
217 return render('changeset/changeset_range.html')
217 return render('changeset/changeset_range.html')
218
218
219 def raw_changeset(self, revision):
219 def raw_changeset(self, revision):
220
220
221 method = request.GET.get('diff', 'show')
221 method = request.GET.get('diff', 'show')
222 try:
222 try:
223 c.scm_type = c.rhodecode_repo.alias
223 c.scm_type = c.rhodecode_repo.alias
224 c.changeset = c.rhodecode_repo.get_changeset(revision)
224 c.changeset = c.rhodecode_repo.get_changeset(revision)
225 except RepositoryError:
225 except RepositoryError:
226 log.error(traceback.format_exc())
226 log.error(traceback.format_exc())
227 return redirect(url('home'))
227 return redirect(url('home'))
228 else:
228 else:
229 try:
229 try:
230 c.changeset_parent = c.changeset.parents[0]
230 c.changeset_parent = c.changeset.parents[0]
231 except IndexError:
231 except IndexError:
232 c.changeset_parent = None
232 c.changeset_parent = None
233 c.changes = []
233 c.changes = []
234
234
235 for node in c.changeset.added:
235 for node in c.changeset.added:
236 filenode_old = FileNode(node.path, '')
236 filenode_old = FileNode(node.path, '')
237 if filenode_old.is_binary or node.is_binary:
237 if filenode_old.is_binary or node.is_binary:
238 diff = _('binary file') + '\n'
238 diff = _('binary file') + '\n'
239 else:
239 else:
240 f_gitdiff = differ.get_gitdiff(filenode_old, node)
240 f_gitdiff = differ.get_gitdiff(filenode_old, node)
241 diff = differ.DiffProcessor(f_gitdiff,
241 diff = differ.DiffProcessor(f_gitdiff,
242 format='gitdiff').raw_diff()
242 format='gitdiff').raw_diff()
243
243
244 cs1 = None
244 cs1 = None
245 cs2 = node.last_changeset.raw_id
245 cs2 = node.last_changeset.raw_id
246 c.changes.append(('added', node, diff, cs1, cs2))
246 c.changes.append(('added', node, diff, cs1, cs2))
247
247
248 for node in c.changeset.changed:
248 for node in c.changeset.changed:
249 filenode_old = c.changeset_parent.get_node(node.path)
249 filenode_old = c.changeset_parent.get_node(node.path)
250 if filenode_old.is_binary or node.is_binary:
250 if filenode_old.is_binary or node.is_binary:
251 diff = _('binary file')
251 diff = _('binary file')
252 else:
252 else:
253 f_gitdiff = differ.get_gitdiff(filenode_old, node)
253 f_gitdiff = differ.get_gitdiff(filenode_old, node)
254 diff = differ.DiffProcessor(f_gitdiff,
254 diff = differ.DiffProcessor(f_gitdiff,
255 format='gitdiff').raw_diff()
255 format='gitdiff').raw_diff()
256
256
257 cs1 = filenode_old.last_changeset.raw_id
257 cs1 = filenode_old.last_changeset.raw_id
258 cs2 = node.last_changeset.raw_id
258 cs2 = node.last_changeset.raw_id
259 c.changes.append(('changed', node, diff, cs1, cs2))
259 c.changes.append(('changed', node, diff, cs1, cs2))
260
260
261 response.content_type = 'text/plain'
261 response.content_type = 'text/plain'
262
262
263 if method == 'download':
263 if method == 'download':
264 response.content_disposition = 'attachment; filename=%s.patch' \
264 response.content_disposition = 'attachment; filename=%s.patch' \
265 % revision
265 % revision
266
266
267 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
267 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
268 c.changeset.parents])
268 c.changeset.parents])
269
269
270 c.diffs = ''
270 c.diffs = ''
271 for x in c.changes:
271 for x in c.changes:
272 c.diffs += x[2]
272 c.diffs += x[2]
273
273
274 return render('changeset/raw_changeset.html')
274 return render('changeset/raw_changeset.html')
275
275
276 def comment(self, repo_name, revision):
276 def comment(self, repo_name, revision):
277 ChangesetCommentsModel().create(text=request.POST.get('text'),
277 ChangesetCommentsModel().create(text=request.POST.get('text'),
278 repo_id=c.rhodecode_db_repo.repo_id,
278 repo_id=c.rhodecode_db_repo.repo_id,
279 user_id=c.rhodecode_user.user_id,
279 user_id=c.rhodecode_user.user_id,
280 revision=revision,
280 revision=revision,
281 f_path=request.POST.get('f_path'),
281 f_path=request.POST.get('f_path'),
282 line_no=request.POST.get('line'))
282 line_no=request.POST.get('line'))
283 Session.commit()
283 Session().commit()
284 return redirect(h.url('changeset_home', repo_name=repo_name,
284 return redirect(h.url('changeset_home', repo_name=repo_name,
285 revision=revision))
285 revision=revision))
286
286
287 @jsonify
287 @jsonify
288 def delete_comment(self, comment_id):
288 def delete_comment(self, repo_name, comment_id):
289 co = ChangesetComment.get(comment_id)
289 co = ChangesetComment.get(comment_id)
290 owner = lambda : co.author.user_id == c.rhodecode_user.user_id
290 owner = lambda : co.author.user_id == c.rhodecode_user.user_id
291 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
291 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
292 ChangesetCommentsModel().delete(comment=co)
292 ChangesetCommentsModel().delete(comment=co)
293 Session.commit()
293 Session().commit()
294 return True
294 return True
295 else:
295 else:
296 raise HTTPForbidden()
296 raise HTTPForbidden()
297
297
@@ -1,678 +1,685 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import random
25 import random
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import hashlib
28 import hashlib
29
29
30 from tempfile import _RandomNameSequence
30 from tempfile import _RandomNameSequence
31 from decorator import decorator
31 from decorator import decorator
32
32
33 from pylons import config, session, url, request
33 from pylons import config, session, url, request
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38
38
39 if __platform__ in PLATFORM_WIN:
39 if __platform__ in PLATFORM_WIN:
40 from hashlib import sha256
40 from hashlib import sha256
41 if __platform__ in PLATFORM_OTHERS:
41 if __platform__ in PLATFORM_OTHERS:
42 import bcrypt
42 import bcrypt
43
43
44 from rhodecode.lib import str2bool, safe_unicode
44 from rhodecode.lib import str2bool, safe_unicode
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 from rhodecode.lib.utils import get_repo_slug
46 from rhodecode.lib.utils import get_repo_slug
47 from rhodecode.lib.auth_ldap import AuthLdap
47 from rhodecode.lib.auth_ldap import AuthLdap
48
48
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission, RhodeCodeSetting, User
51 from rhodecode.model.db import Permission, RhodeCodeSetting, User
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class PasswordGenerator(object):
56 class PasswordGenerator(object):
57 """This is a simple class for generating password from
57 """
58 different sets of characters
58 This is a simple class for generating password from different sets of
59 usage:
59 characters
60 usage::
61
60 passwd_gen = PasswordGenerator()
62 passwd_gen = PasswordGenerator()
61 #print 8-letter password containing only big and small letters
63 #print 8-letter password containing only big and small letters
62 of alphabet
64 of alphabet
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 """
66 """
65 ALPHABETS_NUM = r'''1234567890'''
67 ALPHABETS_NUM = r'''1234567890'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75
77
76 def __init__(self, passwd=''):
78 def __init__(self, passwd=''):
77 self.passwd = passwd
79 self.passwd = passwd
78
80
79 def gen_password(self, len, type):
81 def gen_password(self, len, type):
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
82 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 return self.passwd
83 return self.passwd
82
84
83
85
84 class RhodeCodeCrypto(object):
86 class RhodeCodeCrypto(object):
85
87
86 @classmethod
88 @classmethod
87 def hash_string(cls, str_):
89 def hash_string(cls, str_):
88 """
90 """
89 Cryptographic function used for password hashing based on pybcrypt
91 Cryptographic function used for password hashing based on pybcrypt
90 or pycrypto in windows
92 or pycrypto in windows
91
93
92 :param password: password to hash
94 :param password: password to hash
93 """
95 """
94 if __platform__ in PLATFORM_WIN:
96 if __platform__ in PLATFORM_WIN:
95 return sha256(str_).hexdigest()
97 return sha256(str_).hexdigest()
96 elif __platform__ in PLATFORM_OTHERS:
98 elif __platform__ in PLATFORM_OTHERS:
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
99 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 else:
100 else:
99 raise Exception('Unknown or unsupported platform %s' \
101 raise Exception('Unknown or unsupported platform %s' \
100 % __platform__)
102 % __platform__)
101
103
102 @classmethod
104 @classmethod
103 def hash_check(cls, password, hashed):
105 def hash_check(cls, password, hashed):
104 """
106 """
105 Checks matching password with it's hashed value, runs different
107 Checks matching password with it's hashed value, runs different
106 implementation based on platform it runs on
108 implementation based on platform it runs on
107
109
108 :param password: password
110 :param password: password
109 :param hashed: password in hashed form
111 :param hashed: password in hashed form
110 """
112 """
111
113
112 if __platform__ in PLATFORM_WIN:
114 if __platform__ in PLATFORM_WIN:
113 return sha256(password).hexdigest() == hashed
115 return sha256(password).hexdigest() == hashed
114 elif __platform__ in PLATFORM_OTHERS:
116 elif __platform__ in PLATFORM_OTHERS:
115 return bcrypt.hashpw(password, hashed) == hashed
117 return bcrypt.hashpw(password, hashed) == hashed
116 else:
118 else:
117 raise Exception('Unknown or unsupported platform %s' \
119 raise Exception('Unknown or unsupported platform %s' \
118 % __platform__)
120 % __platform__)
119
121
120
122
121 def get_crypt_password(password):
123 def get_crypt_password(password):
122 return RhodeCodeCrypto.hash_string(password)
124 return RhodeCodeCrypto.hash_string(password)
123
125
124
126
125 def check_password(password, hashed):
127 def check_password(password, hashed):
126 return RhodeCodeCrypto.hash_check(password, hashed)
128 return RhodeCodeCrypto.hash_check(password, hashed)
127
129
128 def generate_api_key(str_, salt=None):
130 def generate_api_key(str_, salt=None):
129 """
131 """
130 Generates API KEY from given string
132 Generates API KEY from given string
131
133
132 :param str_:
134 :param str_:
133 :param salt:
135 :param salt:
134 """
136 """
135
137
136 if salt is None:
138 if salt is None:
137 salt = _RandomNameSequence().next()
139 salt = _RandomNameSequence().next()
138
140
139 return hashlib.sha1(str_ + salt).hexdigest()
141 return hashlib.sha1(str_ + salt).hexdigest()
140
142
141
143
142 def authfunc(environ, username, password):
144 def authfunc(environ, username, password):
143 """
145 """
144 Dummy authentication wrapper function used in Mercurial and Git for
146 Dummy authentication wrapper function used in Mercurial and Git for
145 access control.
147 access control.
146
148
147 :param environ: needed only for using in Basic auth
149 :param environ: needed only for using in Basic auth
148 """
150 """
149 return authenticate(username, password)
151 return authenticate(username, password)
150
152
151
153
152 def authenticate(username, password):
154 def authenticate(username, password):
153 """
155 """
154 Authentication function used for access control,
156 Authentication function used for access control,
155 firstly checks for db authentication then if ldap is enabled for ldap
157 firstly checks for db authentication then if ldap is enabled for ldap
156 authentication, also creates ldap user if not in database
158 authentication, also creates ldap user if not in database
157
159
158 :param username: username
160 :param username: username
159 :param password: password
161 :param password: password
160 """
162 """
161
163
162 user_model = UserModel()
164 user_model = UserModel()
163 user = User.get_by_username(username)
165 user = User.get_by_username(username)
164
166
165 log.debug('Authenticating user using RhodeCode account')
167 log.debug('Authenticating user using RhodeCode account')
166 if user is not None and not user.ldap_dn:
168 if user is not None and not user.ldap_dn:
167 if user.active:
169 if user.active:
168 if user.username == 'default' and user.active:
170 if user.username == 'default' and user.active:
169 log.info('user %s authenticated correctly as anonymous user',
171 log.info('user %s authenticated correctly as anonymous user',
170 username)
172 username)
171 return True
173 return True
172
174
173 elif user.username == username and check_password(password,
175 elif user.username == username and check_password(password,
174 user.password):
176 user.password):
175 log.info('user %s authenticated correctly', username)
177 log.info('user %s authenticated correctly', username)
176 return True
178 return True
177 else:
179 else:
178 log.warning('user %s is disabled', username)
180 log.warning('user %s is disabled', username)
179
181
180 else:
182 else:
181 log.debug('Regular authentication failed')
183 log.debug('Regular authentication failed')
182 user_obj = User.get_by_username(username, case_insensitive=True)
184 user_obj = User.get_by_username(username, case_insensitive=True)
183
185
184 if user_obj is not None and not user_obj.ldap_dn:
186 if user_obj is not None and not user_obj.ldap_dn:
185 log.debug('this user already exists as non ldap')
187 log.debug('this user already exists as non ldap')
186 return False
188 return False
187
189
188 ldap_settings = RhodeCodeSetting.get_ldap_settings()
190 ldap_settings = RhodeCodeSetting.get_ldap_settings()
189 #======================================================================
191 #======================================================================
190 # FALLBACK TO LDAP AUTH IF ENABLE
192 # FALLBACK TO LDAP AUTH IF ENABLE
191 #======================================================================
193 #======================================================================
192 if str2bool(ldap_settings.get('ldap_active')):
194 if str2bool(ldap_settings.get('ldap_active')):
193 log.debug("Authenticating user using ldap")
195 log.debug("Authenticating user using ldap")
194 kwargs = {
196 kwargs = {
195 'server': ldap_settings.get('ldap_host', ''),
197 'server': ldap_settings.get('ldap_host', ''),
196 'base_dn': ldap_settings.get('ldap_base_dn', ''),
198 'base_dn': ldap_settings.get('ldap_base_dn', ''),
197 'port': ldap_settings.get('ldap_port'),
199 'port': ldap_settings.get('ldap_port'),
198 'bind_dn': ldap_settings.get('ldap_dn_user'),
200 'bind_dn': ldap_settings.get('ldap_dn_user'),
199 'bind_pass': ldap_settings.get('ldap_dn_pass'),
201 'bind_pass': ldap_settings.get('ldap_dn_pass'),
200 'tls_kind': ldap_settings.get('ldap_tls_kind'),
202 'tls_kind': ldap_settings.get('ldap_tls_kind'),
201 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
203 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
202 'ldap_filter': ldap_settings.get('ldap_filter'),
204 'ldap_filter': ldap_settings.get('ldap_filter'),
203 'search_scope': ldap_settings.get('ldap_search_scope'),
205 'search_scope': ldap_settings.get('ldap_search_scope'),
204 'attr_login': ldap_settings.get('ldap_attr_login'),
206 'attr_login': ldap_settings.get('ldap_attr_login'),
205 'ldap_version': 3,
207 'ldap_version': 3,
206 }
208 }
207 log.debug('Checking for ldap authentication')
209 log.debug('Checking for ldap authentication')
208 try:
210 try:
209 aldap = AuthLdap(**kwargs)
211 aldap = AuthLdap(**kwargs)
210 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
212 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
211 password)
213 password)
212 log.debug('Got ldap DN response %s', user_dn)
214 log.debug('Got ldap DN response %s', user_dn)
213
215
214 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
216 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
215 .get(k), [''])[0]
217 .get(k), [''])[0]
216
218
217 user_attrs = {
219 user_attrs = {
218 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
220 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
219 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
221 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
220 'email': get_ldap_attr('ldap_attr_email'),
222 'email': get_ldap_attr('ldap_attr_email'),
221 }
223 }
222
224
223 if user_model.create_ldap(username, password, user_dn,
225 if user_model.create_ldap(username, password, user_dn,
224 user_attrs):
226 user_attrs):
225 log.info('created new ldap user %s', username)
227 log.info('created new ldap user %s', username)
226
228
227 return True
229 return True
228 except (LdapUsernameError, LdapPasswordError,):
230 except (LdapUsernameError, LdapPasswordError,):
229 pass
231 pass
230 except (Exception,):
232 except (Exception,):
231 log.error(traceback.format_exc())
233 log.error(traceback.format_exc())
232 pass
234 pass
233 return False
235 return False
234
236
235 def login_container_auth(username):
237 def login_container_auth(username):
236 user = User.get_by_username(username)
238 user = User.get_by_username(username)
237 if user is None:
239 if user is None:
238 user_model = UserModel()
240 user_model = UserModel()
239 user_attrs = {
241 user_attrs = {
240 'name': username,
242 'name': username,
241 'lastname': None,
243 'lastname': None,
242 'email': None,
244 'email': None,
243 }
245 }
244 user = user_model.create_for_container_auth(username, user_attrs)
246 user = user_model.create_for_container_auth(username, user_attrs)
245 if not user:
247 if not user:
246 return None
248 return None
247 log.info('User %s was created by container authentication', username)
249 log.info('User %s was created by container authentication', username)
248
250
249 if not user.active:
251 if not user.active:
250 return None
252 return None
251
253
252 user.update_lastlogin()
254 user.update_lastlogin()
253 log.debug('User %s is now logged in by container authentication',
255 log.debug('User %s is now logged in by container authentication',
254 user.username)
256 user.username)
255 return user
257 return user
256
258
257 def get_container_username(environ, config):
259 def get_container_username(environ, config):
258 username = None
260 username = None
259
261
260 if str2bool(config.get('container_auth_enabled', False)):
262 if str2bool(config.get('container_auth_enabled', False)):
261 from paste.httpheaders import REMOTE_USER
263 from paste.httpheaders import REMOTE_USER
262 username = REMOTE_USER(environ)
264 username = REMOTE_USER(environ)
263
265
264 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
266 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
265 username = environ.get('HTTP_X_FORWARDED_USER')
267 username = environ.get('HTTP_X_FORWARDED_USER')
266
268
267 if username:
269 if username:
268 # Removing realm and domain from username
270 # Removing realm and domain from username
269 username = username.partition('@')[0]
271 username = username.partition('@')[0]
270 username = username.rpartition('\\')[2]
272 username = username.rpartition('\\')[2]
271 log.debug('Received username %s from container', username)
273 log.debug('Received username %s from container', username)
272
274
273 return username
275 return username
274
276
275 class AuthUser(object):
277 class AuthUser(object):
276 """
278 """
277 A simple object that handles all attributes of user in RhodeCode
279 A simple object that handles all attributes of user in RhodeCode
278
280
279 It does lookup based on API key,given user, or user present in session
281 It does lookup based on API key,given user, or user present in session
280 Then it fills all required information for such user. It also checks if
282 Then it fills all required information for such user. It also checks if
281 anonymous access is enabled and if so, it returns default user as logged
283 anonymous access is enabled and if so, it returns default user as logged
282 in
284 in
283 """
285 """
284
286
285 def __init__(self, user_id=None, api_key=None, username=None):
287 def __init__(self, user_id=None, api_key=None, username=None):
286
288
287 self.user_id = user_id
289 self.user_id = user_id
288 self.api_key = None
290 self.api_key = None
289 self.username = username
291 self.username = username
290
292
291 self.name = ''
293 self.name = ''
292 self.lastname = ''
294 self.lastname = ''
293 self.email = ''
295 self.email = ''
294 self.is_authenticated = False
296 self.is_authenticated = False
295 self.admin = False
297 self.admin = False
296 self.permissions = {}
298 self.permissions = {}
297 self._api_key = api_key
299 self._api_key = api_key
298 self.propagate_data()
300 self.propagate_data()
299
301
300 def propagate_data(self):
302 def propagate_data(self):
301 user_model = UserModel()
303 user_model = UserModel()
302 self.anonymous_user = User.get_by_username('default')
304 self.anonymous_user = User.get_by_username('default')
303 is_user_loaded = False
305 is_user_loaded = False
304
306
305 # try go get user by api key
307 # try go get user by api key
306 if self._api_key and self._api_key != self.anonymous_user.api_key:
308 if self._api_key and self._api_key != self.anonymous_user.api_key:
307 log.debug('Auth User lookup by API KEY %s', self._api_key)
309 log.debug('Auth User lookup by API KEY %s', self._api_key)
308 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
310 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
309 # lookup by userid
311 # lookup by userid
310 elif (self.user_id is not None and
312 elif (self.user_id is not None and
311 self.user_id != self.anonymous_user.user_id):
313 self.user_id != self.anonymous_user.user_id):
312 log.debug('Auth User lookup by USER ID %s', self.user_id)
314 log.debug('Auth User lookup by USER ID %s', self.user_id)
313 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
315 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
314 # lookup by username
316 # lookup by username
315 elif self.username:
317 elif self.username:
316 log.debug('Auth User lookup by USER NAME %s', self.username)
318 log.debug('Auth User lookup by USER NAME %s', self.username)
317 dbuser = login_container_auth(self.username)
319 dbuser = login_container_auth(self.username)
318 if dbuser is not None:
320 if dbuser is not None:
319 for k, v in dbuser.get_dict().items():
321 for k, v in dbuser.get_dict().items():
320 setattr(self, k, v)
322 setattr(self, k, v)
321 self.set_authenticated()
323 self.set_authenticated()
322 is_user_loaded = True
324 is_user_loaded = True
323
325
324 if not is_user_loaded:
326 if not is_user_loaded:
325 # if we cannot authenticate user try anonymous
327 # if we cannot authenticate user try anonymous
326 if self.anonymous_user.active is True:
328 if self.anonymous_user.active is True:
327 user_model.fill_data(self,user_id=self.anonymous_user.user_id)
329 user_model.fill_data(self,user_id=self.anonymous_user.user_id)
328 # then we set this user is logged in
330 # then we set this user is logged in
329 self.is_authenticated = True
331 self.is_authenticated = True
330 else:
332 else:
331 self.user_id = None
333 self.user_id = None
332 self.username = None
334 self.username = None
333 self.is_authenticated = False
335 self.is_authenticated = False
334
336
335 if not self.username:
337 if not self.username:
336 self.username = 'None'
338 self.username = 'None'
337
339
338 log.debug('Auth User is now %s', self)
340 log.debug('Auth User is now %s', self)
339 user_model.fill_perms(self)
341 user_model.fill_perms(self)
340
342
341 @property
343 @property
342 def is_admin(self):
344 def is_admin(self):
343 return self.admin
345 return self.admin
344
346
345 @property
347 @property
346 def full_contact(self):
348 def full_contact(self):
347 return '%s %s <%s>' % (self.name, self.lastname, self.email)
349 return '%s %s <%s>' % (self.name, self.lastname, self.email)
348
350
349 def __repr__(self):
351 def __repr__(self):
350 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
352 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
351 self.is_authenticated)
353 self.is_authenticated)
352
354
353 def set_authenticated(self, authenticated=True):
355 def set_authenticated(self, authenticated=True):
354 if self.user_id != self.anonymous_user.user_id:
356 if self.user_id != self.anonymous_user.user_id:
355 self.is_authenticated = authenticated
357 self.is_authenticated = authenticated
356
358
357
359
358 def set_available_permissions(config):
360 def set_available_permissions(config):
359 """
361 """
360 This function will propagate pylons globals with all available defined
362 This function will propagate pylons globals with all available defined
361 permission given in db. We don't want to check each time from db for new
363 permission given in db. We don't want to check each time from db for new
362 permissions since adding a new permission also requires application restart
364 permissions since adding a new permission also requires application restart
363 ie. to decorate new views with the newly created permission
365 ie. to decorate new views with the newly created permission
364
366
365 :param config: current pylons config instance
367 :param config: current pylons config instance
366
368
367 """
369 """
368 log.info('getting information about all available permissions')
370 log.info('getting information about all available permissions')
369 try:
371 try:
370 sa = meta.Session()
372 sa = meta.Session()
371 all_perms = sa.query(Permission).all()
373 all_perms = sa.query(Permission).all()
372 except:
374 except:
373 pass
375 pass
374 finally:
376 finally:
375 meta.Session.remove()
377 meta.Session.remove()
376
378
377 config['available_permissions'] = [x.permission_name for x in all_perms]
379 config['available_permissions'] = [x.permission_name for x in all_perms]
378
380
379
381
380 #==============================================================================
382 #==============================================================================
381 # CHECK DECORATORS
383 # CHECK DECORATORS
382 #==============================================================================
384 #==============================================================================
383 class LoginRequired(object):
385 class LoginRequired(object):
384 """
386 """
385 Must be logged in to execute this function else
387 Must be logged in to execute this function else
386 redirect to login page
388 redirect to login page
387
389
388 :param api_access: if enabled this checks only for valid auth token
390 :param api_access: if enabled this checks only for valid auth token
389 and grants access based on valid token
391 and grants access based on valid token
390 """
392 """
391
393
392 def __init__(self, api_access=False):
394 def __init__(self, api_access=False):
393 self.api_access = api_access
395 self.api_access = api_access
394
396
395 def __call__(self, func):
397 def __call__(self, func):
396 return decorator(self.__wrapper, func)
398 return decorator(self.__wrapper, func)
397
399
398 def __wrapper(self, func, *fargs, **fkwargs):
400 def __wrapper(self, func, *fargs, **fkwargs):
399 cls = fargs[0]
401 cls = fargs[0]
400 user = cls.rhodecode_user
402 user = cls.rhodecode_user
401
403
402 api_access_ok = False
404 api_access_ok = False
403 if self.api_access:
405 if self.api_access:
404 log.debug('Checking API KEY access for %s', cls)
406 log.debug('Checking API KEY access for %s', cls)
405 if user.api_key == request.GET.get('api_key'):
407 if user.api_key == request.GET.get('api_key'):
406 api_access_ok = True
408 api_access_ok = True
407 else:
409 else:
408 log.debug("API KEY token not valid")
410 log.debug("API KEY token not valid")
409
411
410 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
412 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
411 if user.is_authenticated or api_access_ok:
413 if user.is_authenticated or api_access_ok:
412 log.debug('user %s is authenticated', user.username)
414 log.debug('user %s is authenticated', user.username)
413 return func(*fargs, **fkwargs)
415 return func(*fargs, **fkwargs)
414 else:
416 else:
415 log.warn('user %s NOT authenticated', user.username)
417 log.warn('user %s NOT authenticated', user.username)
416 p = url.current()
418 p = url.current()
417
419
418 log.debug('redirecting to login page with %s', p)
420 log.debug('redirecting to login page with %s', p)
419 return redirect(url('login_home', came_from=p))
421 return redirect(url('login_home', came_from=p))
420
422
421
423
422 class NotAnonymous(object):
424 class NotAnonymous(object):
423 """Must be logged in to execute this function else
425 """
426 Must be logged in to execute this function else
424 redirect to login page"""
427 redirect to login page"""
425
428
426 def __call__(self, func):
429 def __call__(self, func):
427 return decorator(self.__wrapper, func)
430 return decorator(self.__wrapper, func)
428
431
429 def __wrapper(self, func, *fargs, **fkwargs):
432 def __wrapper(self, func, *fargs, **fkwargs):
430 cls = fargs[0]
433 cls = fargs[0]
431 self.user = cls.rhodecode_user
434 self.user = cls.rhodecode_user
432
435
433 log.debug('Checking if user is not anonymous @%s', cls)
436 log.debug('Checking if user is not anonymous @%s', cls)
434
437
435 anonymous = self.user.username == 'default'
438 anonymous = self.user.username == 'default'
436
439
437 if anonymous:
440 if anonymous:
438 p = url.current()
441 p = url.current()
439
442
440 import rhodecode.lib.helpers as h
443 import rhodecode.lib.helpers as h
441 h.flash(_('You need to be a registered user to '
444 h.flash(_('You need to be a registered user to '
442 'perform this action'),
445 'perform this action'),
443 category='warning')
446 category='warning')
444 return redirect(url('login_home', came_from=p))
447 return redirect(url('login_home', came_from=p))
445 else:
448 else:
446 return func(*fargs, **fkwargs)
449 return func(*fargs, **fkwargs)
447
450
448
451
449 class PermsDecorator(object):
452 class PermsDecorator(object):
450 """Base class for controller decorators"""
453 """Base class for controller decorators"""
451
454
452 def __init__(self, *required_perms):
455 def __init__(self, *required_perms):
453 available_perms = config['available_permissions']
456 available_perms = config['available_permissions']
454 for perm in required_perms:
457 for perm in required_perms:
455 if perm not in available_perms:
458 if perm not in available_perms:
456 raise Exception("'%s' permission is not defined" % perm)
459 raise Exception("'%s' permission is not defined" % perm)
457 self.required_perms = set(required_perms)
460 self.required_perms = set(required_perms)
458 self.user_perms = None
461 self.user_perms = None
459
462
460 def __call__(self, func):
463 def __call__(self, func):
461 return decorator(self.__wrapper, func)
464 return decorator(self.__wrapper, func)
462
465
463 def __wrapper(self, func, *fargs, **fkwargs):
466 def __wrapper(self, func, *fargs, **fkwargs):
464 cls = fargs[0]
467 cls = fargs[0]
465 self.user = cls.rhodecode_user
468 self.user = cls.rhodecode_user
466 self.user_perms = self.user.permissions
469 self.user_perms = self.user.permissions
467 log.debug('checking %s permissions %s for %s %s',
470 log.debug('checking %s permissions %s for %s %s',
468 self.__class__.__name__, self.required_perms, cls,
471 self.__class__.__name__, self.required_perms, cls,
469 self.user)
472 self.user)
470
473
471 if self.check_permissions():
474 if self.check_permissions():
472 log.debug('Permission granted for %s %s', cls, self.user)
475 log.debug('Permission granted for %s %s', cls, self.user)
473 return func(*fargs, **fkwargs)
476 return func(*fargs, **fkwargs)
474
477
475 else:
478 else:
476 log.warning('Permission denied for %s %s', cls, self.user)
479 log.warning('Permission denied for %s %s', cls, self.user)
477
480
478
481
479 anonymous = self.user.username == 'default'
482 anonymous = self.user.username == 'default'
480
483
481 if anonymous:
484 if anonymous:
482 p = url.current()
485 p = url.current()
483
486
484 import rhodecode.lib.helpers as h
487 import rhodecode.lib.helpers as h
485 h.flash(_('You need to be a signed in to '
488 h.flash(_('You need to be a signed in to '
486 'view this page'),
489 'view this page'),
487 category='warning')
490 category='warning')
488 return redirect(url('login_home', came_from=p))
491 return redirect(url('login_home', came_from=p))
489
492
490 else:
493 else:
491 # redirect with forbidden ret code
494 # redirect with forbidden ret code
492 return abort(403)
495 return abort(403)
493
496
494 def check_permissions(self):
497 def check_permissions(self):
495 """Dummy function for overriding"""
498 """Dummy function for overriding"""
496 raise Exception('You have to write this function in child class')
499 raise Exception('You have to write this function in child class')
497
500
498
501
499 class HasPermissionAllDecorator(PermsDecorator):
502 class HasPermissionAllDecorator(PermsDecorator):
500 """Checks for access permission for all given predicates. All of them
503 """
504 Checks for access permission for all given predicates. All of them
501 have to be meet in order to fulfill the request
505 have to be meet in order to fulfill the request
502 """
506 """
503
507
504 def check_permissions(self):
508 def check_permissions(self):
505 if self.required_perms.issubset(self.user_perms.get('global')):
509 if self.required_perms.issubset(self.user_perms.get('global')):
506 return True
510 return True
507 return False
511 return False
508
512
509
513
510 class HasPermissionAnyDecorator(PermsDecorator):
514 class HasPermissionAnyDecorator(PermsDecorator):
511 """Checks for access permission for any of given predicates. In order to
515 """
516 Checks for access permission for any of given predicates. In order to
512 fulfill the request any of predicates must be meet
517 fulfill the request any of predicates must be meet
513 """
518 """
514
519
515 def check_permissions(self):
520 def check_permissions(self):
516 if self.required_perms.intersection(self.user_perms.get('global')):
521 if self.required_perms.intersection(self.user_perms.get('global')):
517 return True
522 return True
518 return False
523 return False
519
524
520
525
521 class HasRepoPermissionAllDecorator(PermsDecorator):
526 class HasRepoPermissionAllDecorator(PermsDecorator):
522 """Checks for access permission for all given predicates for specific
527 """
528 Checks for access permission for all given predicates for specific
523 repository. All of them have to be meet in order to fulfill the request
529 repository. All of them have to be meet in order to fulfill the request
524 """
530 """
525
531
526 def check_permissions(self):
532 def check_permissions(self):
527 repo_name = get_repo_slug(request)
533 repo_name = get_repo_slug(request)
528 try:
534 try:
529 user_perms = set([self.user_perms['repositories'][repo_name]])
535 user_perms = set([self.user_perms['repositories'][repo_name]])
530 except KeyError:
536 except KeyError:
531 return False
537 return False
532 if self.required_perms.issubset(user_perms):
538 if self.required_perms.issubset(user_perms):
533 return True
539 return True
534 return False
540 return False
535
541
536
542
537 class HasRepoPermissionAnyDecorator(PermsDecorator):
543 class HasRepoPermissionAnyDecorator(PermsDecorator):
538 """Checks for access permission for any of given predicates for specific
544 """
545 Checks for access permission for any of given predicates for specific
539 repository. In order to fulfill the request any of predicates must be meet
546 repository. In order to fulfill the request any of predicates must be meet
540 """
547 """
541
548
542 def check_permissions(self):
549 def check_permissions(self):
543 repo_name = get_repo_slug(request)
550 repo_name = get_repo_slug(request)
544
551
545 try:
552 try:
546 user_perms = set([self.user_perms['repositories'][repo_name]])
553 user_perms = set([self.user_perms['repositories'][repo_name]])
547 except KeyError:
554 except KeyError:
548 return False
555 return False
549 if self.required_perms.intersection(user_perms):
556 if self.required_perms.intersection(user_perms):
550 return True
557 return True
551 return False
558 return False
552
559
553
560
554 #==============================================================================
561 #==============================================================================
555 # CHECK FUNCTIONS
562 # CHECK FUNCTIONS
556 #==============================================================================
563 #==============================================================================
557 class PermsFunction(object):
564 class PermsFunction(object):
558 """Base function for other check functions"""
565 """Base function for other check functions"""
559
566
560 def __init__(self, *perms):
567 def __init__(self, *perms):
561 available_perms = config['available_permissions']
568 available_perms = config['available_permissions']
562
569
563 for perm in perms:
570 for perm in perms:
564 if perm not in available_perms:
571 if perm not in available_perms:
565 raise Exception("'%s' permission in not defined" % perm)
572 raise Exception("'%s' permission in not defined" % perm)
566 self.required_perms = set(perms)
573 self.required_perms = set(perms)
567 self.user_perms = None
574 self.user_perms = None
568 self.granted_for = ''
575 self.granted_for = ''
569 self.repo_name = None
576 self.repo_name = None
570
577
571 def __call__(self, check_Location=''):
578 def __call__(self, check_Location=''):
572 user = session.get('rhodecode_user', False)
579 user = session.get('rhodecode_user', False)
573 if not user:
580 if not user:
574 return False
581 return False
575 self.user_perms = user.permissions
582 self.user_perms = user.permissions
576 self.granted_for = user
583 self.granted_for = user
577 log.debug('checking %s %s %s', self.__class__.__name__,
584 log.debug('checking %s %s %s', self.__class__.__name__,
578 self.required_perms, user)
585 self.required_perms, user)
579
586
580 if self.check_permissions():
587 if self.check_permissions():
581 log.debug('Permission granted %s @ %s', self.granted_for,
588 log.debug('Permission granted %s @ %s', self.granted_for,
582 check_Location or 'unspecified location')
589 check_Location or 'unspecified location')
583 return True
590 return True
584
591
585 else:
592 else:
586 log.warning('Permission denied for %s @ %s', self.granted_for,
593 log.warning('Permission denied for %s @ %s', self.granted_for,
587 check_Location or 'unspecified location')
594 check_Location or 'unspecified location')
588 return False
595 return False
589
596
590 def check_permissions(self):
597 def check_permissions(self):
591 """Dummy function for overriding"""
598 """Dummy function for overriding"""
592 raise Exception('You have to write this function in child class')
599 raise Exception('You have to write this function in child class')
593
600
594
601
595 class HasPermissionAll(PermsFunction):
602 class HasPermissionAll(PermsFunction):
596 def check_permissions(self):
603 def check_permissions(self):
597 if self.required_perms.issubset(self.user_perms.get('global')):
604 if self.required_perms.issubset(self.user_perms.get('global')):
598 return True
605 return True
599 return False
606 return False
600
607
601
608
602 class HasPermissionAny(PermsFunction):
609 class HasPermissionAny(PermsFunction):
603 def check_permissions(self):
610 def check_permissions(self):
604 if self.required_perms.intersection(self.user_perms.get('global')):
611 if self.required_perms.intersection(self.user_perms.get('global')):
605 return True
612 return True
606 return False
613 return False
607
614
608
615
609 class HasRepoPermissionAll(PermsFunction):
616 class HasRepoPermissionAll(PermsFunction):
610
617
611 def __call__(self, repo_name=None, check_Location=''):
618 def __call__(self, repo_name=None, check_Location=''):
612 self.repo_name = repo_name
619 self.repo_name = repo_name
613 return super(HasRepoPermissionAll, self).__call__(check_Location)
620 return super(HasRepoPermissionAll, self).__call__(check_Location)
614
621
615 def check_permissions(self):
622 def check_permissions(self):
616 if not self.repo_name:
623 if not self.repo_name:
617 self.repo_name = get_repo_slug(request)
624 self.repo_name = get_repo_slug(request)
618
625
619 try:
626 try:
620 self.user_perms = set([self.user_perms['reposit'
627 self.user_perms = set([self.user_perms['reposit'
621 'ories'][self.repo_name]])
628 'ories'][self.repo_name]])
622 except KeyError:
629 except KeyError:
623 return False
630 return False
624 self.granted_for = self.repo_name
631 self.granted_for = self.repo_name
625 if self.required_perms.issubset(self.user_perms):
632 if self.required_perms.issubset(self.user_perms):
626 return True
633 return True
627 return False
634 return False
628
635
629
636
630 class HasRepoPermissionAny(PermsFunction):
637 class HasRepoPermissionAny(PermsFunction):
631
638
632 def __call__(self, repo_name=None, check_Location=''):
639 def __call__(self, repo_name=None, check_Location=''):
633 self.repo_name = repo_name
640 self.repo_name = repo_name
634 return super(HasRepoPermissionAny, self).__call__(check_Location)
641 return super(HasRepoPermissionAny, self).__call__(check_Location)
635
642
636 def check_permissions(self):
643 def check_permissions(self):
637 if not self.repo_name:
644 if not self.repo_name:
638 self.repo_name = get_repo_slug(request)
645 self.repo_name = get_repo_slug(request)
639
646
640 try:
647 try:
641 self.user_perms = set([self.user_perms['reposi'
648 self.user_perms = set([self.user_perms['reposi'
642 'tories'][self.repo_name]])
649 'tories'][self.repo_name]])
643 except KeyError:
650 except KeyError:
644 return False
651 return False
645 self.granted_for = self.repo_name
652 self.granted_for = self.repo_name
646 if self.required_perms.intersection(self.user_perms):
653 if self.required_perms.intersection(self.user_perms):
647 return True
654 return True
648 return False
655 return False
649
656
650
657
651 #==============================================================================
658 #==============================================================================
652 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
659 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
653 #==============================================================================
660 #==============================================================================
654 class HasPermissionAnyMiddleware(object):
661 class HasPermissionAnyMiddleware(object):
655 def __init__(self, *perms):
662 def __init__(self, *perms):
656 self.required_perms = set(perms)
663 self.required_perms = set(perms)
657
664
658 def __call__(self, user, repo_name):
665 def __call__(self, user, repo_name):
659 usr = AuthUser(user.user_id)
666 usr = AuthUser(user.user_id)
660 try:
667 try:
661 self.user_perms = set([usr.permissions['repositories'][repo_name]])
668 self.user_perms = set([usr.permissions['repositories'][repo_name]])
662 except:
669 except:
663 self.user_perms = set()
670 self.user_perms = set()
664 self.granted_for = ''
671 self.granted_for = ''
665 self.username = user.username
672 self.username = user.username
666 self.repo_name = repo_name
673 self.repo_name = repo_name
667 return self.check_permissions()
674 return self.check_permissions()
668
675
669 def check_permissions(self):
676 def check_permissions(self):
670 log.debug('checking mercurial protocol '
677 log.debug('checking mercurial protocol '
671 'permissions %s for user:%s repository:%s', self.user_perms,
678 'permissions %s for user:%s repository:%s', self.user_perms,
672 self.username, self.repo_name)
679 self.username, self.repo_name)
673 if self.required_perms.intersection(self.user_perms):
680 if self.required_perms.intersection(self.user_perms):
674 log.debug('permission granted')
681 log.debug('permission granted')
675 return True
682 return True
676 log.debug('permission denied')
683 log.debug('permission denied')
677 return False
684 return False
678
685
@@ -1,499 +1,499 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35
35
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
38 from rhodecode.model import init_model
38 from rhodecode.model import init_model
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion
41
41
42 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class DbManage(object):
47 class DbManage(object):
48 def __init__(self, log_sql, dbconf, root, tests=False):
48 def __init__(self, log_sql, dbconf, root, tests=False):
49 self.dbname = dbconf.split('/')[-1]
49 self.dbname = dbconf.split('/')[-1]
50 self.tests = tests
50 self.tests = tests
51 self.root = root
51 self.root = root
52 self.dburi = dbconf
52 self.dburi = dbconf
53 self.log_sql = log_sql
53 self.log_sql = log_sql
54 self.db_exists = False
54 self.db_exists = False
55 self.init_db()
55 self.init_db()
56
56
57 def init_db(self):
57 def init_db(self):
58 engine = create_engine(self.dburi, echo=self.log_sql)
58 engine = create_engine(self.dburi, echo=self.log_sql)
59 init_model(engine)
59 init_model(engine)
60 self.sa = meta.Session()
60 self.sa = meta.Session()
61
61
62 def create_tables(self, override=False):
62 def create_tables(self, override=False):
63 """Create a auth database
63 """Create a auth database
64 """
64 """
65
65
66 log.info("Any existing database is going to be destroyed")
66 log.info("Any existing database is going to be destroyed")
67 if self.tests:
67 if self.tests:
68 destroy = True
68 destroy = True
69 else:
69 else:
70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 if not destroy:
71 if not destroy:
72 sys.exit()
72 sys.exit()
73 if destroy:
73 if destroy:
74 meta.Base.metadata.drop_all()
74 meta.Base.metadata.drop_all()
75
75
76 checkfirst = not override
76 checkfirst = not override
77 meta.Base.metadata.create_all(checkfirst=checkfirst)
77 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 log.info('Created tables for %s', self.dbname)
78 log.info('Created tables for %s', self.dbname)
79
79
80 def set_db_version(self):
80 def set_db_version(self):
81 try:
81 try:
82 ver = DbMigrateVersion()
82 ver = DbMigrateVersion()
83 ver.version = __dbversion__
83 ver.version = __dbversion__
84 ver.repository_id = 'rhodecode_db_migrations'
84 ver.repository_id = 'rhodecode_db_migrations'
85 ver.repository_path = 'versions'
85 ver.repository_path = 'versions'
86 self.sa.add(ver)
86 self.sa.add(ver)
87 self.sa.commit()
87 self.sa.commit()
88 except:
88 except:
89 self.sa.rollback()
89 self.sa.rollback()
90 raise
90 raise
91 log.info('db version set to: %s', __dbversion__)
91 log.info('db version set to: %s', __dbversion__)
92
92
93 def upgrade(self):
93 def upgrade(self):
94 """Upgrades given database schema to given revision following
94 """Upgrades given database schema to given revision following
95 all needed steps, to perform the upgrade
95 all needed steps, to perform the upgrade
96
96
97 """
97 """
98
98
99 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 from rhodecode.lib.dbmigrate.migrate.versioning import api
100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 from rhodecode.lib.dbmigrate.migrate.exceptions import \
101 DatabaseNotControlledError
101 DatabaseNotControlledError
102
102
103 upgrade = ask_ok('You are about to perform database upgrade, make '
103 upgrade = ask_ok('You are about to perform database upgrade, make '
104 'sure You backed up your database before. '
104 'sure You backed up your database before. '
105 'Continue ? [y/n]')
105 'Continue ? [y/n]')
106 if not upgrade:
106 if not upgrade:
107 sys.exit('Nothing done')
107 sys.exit('Nothing done')
108
108
109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
109 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
110 'rhodecode/lib/dbmigrate')
110 'rhodecode/lib/dbmigrate')
111 db_uri = self.dburi
111 db_uri = self.dburi
112
112
113 try:
113 try:
114 curr_version = api.db_version(db_uri, repository_path)
114 curr_version = api.db_version(db_uri, repository_path)
115 msg = ('Found current database under version'
115 msg = ('Found current database under version'
116 ' control with version %s' % curr_version)
116 ' control with version %s' % curr_version)
117
117
118 except (RuntimeError, DatabaseNotControlledError):
118 except (RuntimeError, DatabaseNotControlledError):
119 curr_version = 1
119 curr_version = 1
120 msg = ('Current database is not under version control. Setting'
120 msg = ('Current database is not under version control. Setting'
121 ' as version %s' % curr_version)
121 ' as version %s' % curr_version)
122 api.version_control(db_uri, repository_path, curr_version)
122 api.version_control(db_uri, repository_path, curr_version)
123
123
124 print (msg)
124 print (msg)
125
125
126 if curr_version == __dbversion__:
126 if curr_version == __dbversion__:
127 sys.exit('This database is already at the newest version')
127 sys.exit('This database is already at the newest version')
128
128
129 #======================================================================
129 #======================================================================
130 # UPGRADE STEPS
130 # UPGRADE STEPS
131 #======================================================================
131 #======================================================================
132 class UpgradeSteps(object):
132 class UpgradeSteps(object):
133 """Those steps follow schema versions so for example schema
133 """Those steps follow schema versions so for example schema
134 for example schema with seq 002 == step_2 and so on.
134 for example schema with seq 002 == step_2 and so on.
135 """
135 """
136
136
137 def __init__(self, klass):
137 def __init__(self, klass):
138 self.klass = klass
138 self.klass = klass
139
139
140 def step_0(self):
140 def step_0(self):
141 #step 0 is the schema upgrade, and than follow proper upgrades
141 #step 0 is the schema upgrade, and than follow proper upgrades
142 print ('attempting to do database upgrade to version %s' \
142 print ('attempting to do database upgrade to version %s' \
143 % __dbversion__)
143 % __dbversion__)
144 api.upgrade(db_uri, repository_path, __dbversion__)
144 api.upgrade(db_uri, repository_path, __dbversion__)
145 print ('Schema upgrade completed')
145 print ('Schema upgrade completed')
146
146
147 def step_1(self):
147 def step_1(self):
148 pass
148 pass
149
149
150 def step_2(self):
150 def step_2(self):
151 print ('Patching repo paths for newer version of RhodeCode')
151 print ('Patching repo paths for newer version of RhodeCode')
152 self.klass.fix_repo_paths()
152 self.klass.fix_repo_paths()
153
153
154 print ('Patching default user of RhodeCode')
154 print ('Patching default user of RhodeCode')
155 self.klass.fix_default_user()
155 self.klass.fix_default_user()
156
156
157 log.info('Changing ui settings')
157 log.info('Changing ui settings')
158 self.klass.create_ui_settings()
158 self.klass.create_ui_settings()
159
159
160 def step_3(self):
160 def step_3(self):
161 print ('Adding additional settings into RhodeCode db')
161 print ('Adding additional settings into RhodeCode db')
162 self.klass.fix_settings()
162 self.klass.fix_settings()
163 print ('Adding ldap defaults')
163 print ('Adding ldap defaults')
164 self.klass.create_ldap_options(skip_existing=True)
164 self.klass.create_ldap_options(skip_existing=True)
165
165
166 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
166 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
167
167
168 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
168 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
169 for step in upgrade_steps:
169 for step in upgrade_steps:
170 print ('performing upgrade step %s' % step)
170 print ('performing upgrade step %s' % step)
171 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
171 getattr(UpgradeSteps(self), 'step_%s' % step)()
172
172
173 def fix_repo_paths(self):
173 def fix_repo_paths(self):
174 """Fixes a old rhodecode version path into new one without a '*'
174 """Fixes a old rhodecode version path into new one without a '*'
175 """
175 """
176
176
177 paths = self.sa.query(RhodeCodeUi)\
177 paths = self.sa.query(RhodeCodeUi)\
178 .filter(RhodeCodeUi.ui_key == '/')\
178 .filter(RhodeCodeUi.ui_key == '/')\
179 .scalar()
179 .scalar()
180
180
181 paths.ui_value = paths.ui_value.replace('*', '')
181 paths.ui_value = paths.ui_value.replace('*', '')
182
182
183 try:
183 try:
184 self.sa.add(paths)
184 self.sa.add(paths)
185 self.sa.commit()
185 self.sa.commit()
186 except:
186 except:
187 self.sa.rollback()
187 self.sa.rollback()
188 raise
188 raise
189
189
190 def fix_default_user(self):
190 def fix_default_user(self):
191 """Fixes a old default user with some 'nicer' default values,
191 """Fixes a old default user with some 'nicer' default values,
192 used mostly for anonymous access
192 used mostly for anonymous access
193 """
193 """
194 def_user = self.sa.query(User)\
194 def_user = self.sa.query(User)\
195 .filter(User.username == 'default')\
195 .filter(User.username == 'default')\
196 .one()
196 .one()
197
197
198 def_user.name = 'Anonymous'
198 def_user.name = 'Anonymous'
199 def_user.lastname = 'User'
199 def_user.lastname = 'User'
200 def_user.email = 'anonymous@rhodecode.org'
200 def_user.email = 'anonymous@rhodecode.org'
201
201
202 try:
202 try:
203 self.sa.add(def_user)
203 self.sa.add(def_user)
204 self.sa.commit()
204 self.sa.commit()
205 except:
205 except:
206 self.sa.rollback()
206 self.sa.rollback()
207 raise
207 raise
208
208
209 def fix_settings(self):
209 def fix_settings(self):
210 """Fixes rhodecode settings adds ga_code key for google analytics
210 """Fixes rhodecode settings adds ga_code key for google analytics
211 """
211 """
212
212
213 hgsettings3 = RhodeCodeSetting('ga_code', '')
213 hgsettings3 = RhodeCodeSetting('ga_code', '')
214
214
215 try:
215 try:
216 self.sa.add(hgsettings3)
216 self.sa.add(hgsettings3)
217 self.sa.commit()
217 self.sa.commit()
218 except:
218 except:
219 self.sa.rollback()
219 self.sa.rollback()
220 raise
220 raise
221
221
222 def admin_prompt(self, second=False):
222 def admin_prompt(self, second=False):
223 if not self.tests:
223 if not self.tests:
224 import getpass
224 import getpass
225
225
226 def get_password():
226 def get_password():
227 password = getpass.getpass('Specify admin password '
227 password = getpass.getpass('Specify admin password '
228 '(min 6 chars):')
228 '(min 6 chars):')
229 confirm = getpass.getpass('Confirm password:')
229 confirm = getpass.getpass('Confirm password:')
230
230
231 if password != confirm:
231 if password != confirm:
232 log.error('passwords mismatch')
232 log.error('passwords mismatch')
233 return False
233 return False
234 if len(password) < 6:
234 if len(password) < 6:
235 log.error('password is to short use at least 6 characters')
235 log.error('password is to short use at least 6 characters')
236 return False
236 return False
237
237
238 return password
238 return password
239
239
240 username = raw_input('Specify admin username:')
240 username = raw_input('Specify admin username:')
241
241
242 password = get_password()
242 password = get_password()
243 if not password:
243 if not password:
244 #second try
244 #second try
245 password = get_password()
245 password = get_password()
246 if not password:
246 if not password:
247 sys.exit()
247 sys.exit()
248
248
249 email = raw_input('Specify admin email:')
249 email = raw_input('Specify admin email:')
250 self.create_user(username, password, email, True)
250 self.create_user(username, password, email, True)
251 else:
251 else:
252 log.info('creating admin and regular test users')
252 log.info('creating admin and regular test users')
253 self.create_user('test_admin', 'test12',
253 self.create_user('test_admin', 'test12',
254 'test_admin@mail.com', True)
254 'test_admin@mail.com', True)
255 self.create_user('test_regular', 'test12',
255 self.create_user('test_regular', 'test12',
256 'test_regular@mail.com', False)
256 'test_regular@mail.com', False)
257 self.create_user('test_regular2', 'test12',
257 self.create_user('test_regular2', 'test12',
258 'test_regular2@mail.com', False)
258 'test_regular2@mail.com', False)
259
259
260 def create_ui_settings(self):
260 def create_ui_settings(self):
261 """Creates ui settings, fills out hooks
261 """Creates ui settings, fills out hooks
262 and disables dotencode
262 and disables dotencode
263
263
264 """
264 """
265 #HOOKS
265 #HOOKS
266 hooks1_key = RhodeCodeUi.HOOK_UPDATE
266 hooks1_key = RhodeCodeUi.HOOK_UPDATE
267 hooks1_ = self.sa.query(RhodeCodeUi)\
267 hooks1_ = self.sa.query(RhodeCodeUi)\
268 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
268 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
269
269
270 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
270 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
271 hooks1.ui_section = 'hooks'
271 hooks1.ui_section = 'hooks'
272 hooks1.ui_key = hooks1_key
272 hooks1.ui_key = hooks1_key
273 hooks1.ui_value = 'hg update >&2'
273 hooks1.ui_value = 'hg update >&2'
274 hooks1.ui_active = False
274 hooks1.ui_active = False
275
275
276 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
276 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
277 hooks2_ = self.sa.query(RhodeCodeUi)\
277 hooks2_ = self.sa.query(RhodeCodeUi)\
278 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
278 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
279
279
280 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
280 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
281 hooks2.ui_section = 'hooks'
281 hooks2.ui_section = 'hooks'
282 hooks2.ui_key = hooks2_key
282 hooks2.ui_key = hooks2_key
283 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
283 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
284
284
285 hooks3 = RhodeCodeUi()
285 hooks3 = RhodeCodeUi()
286 hooks3.ui_section = 'hooks'
286 hooks3.ui_section = 'hooks'
287 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
287 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
288 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
288 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
289
289
290 hooks4 = RhodeCodeUi()
290 hooks4 = RhodeCodeUi()
291 hooks4.ui_section = 'hooks'
291 hooks4.ui_section = 'hooks'
292 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
292 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
293 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
293 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
294
294
295 # For mercurial 1.7 set backward comapatibility with format
295 # For mercurial 1.7 set backward comapatibility with format
296 dotencode_disable = RhodeCodeUi()
296 dotencode_disable = RhodeCodeUi()
297 dotencode_disable.ui_section = 'format'
297 dotencode_disable.ui_section = 'format'
298 dotencode_disable.ui_key = 'dotencode'
298 dotencode_disable.ui_key = 'dotencode'
299 dotencode_disable.ui_value = 'false'
299 dotencode_disable.ui_value = 'false'
300
300
301 # enable largefiles
301 # enable largefiles
302 dotencode_disable = RhodeCodeUi()
302 dotencode_disable = RhodeCodeUi()
303 dotencode_disable.ui_section = 'extensions'
303 dotencode_disable.ui_section = 'extensions'
304 dotencode_disable.ui_key = 'largefiles'
304 dotencode_disable.ui_key = 'largefiles'
305 dotencode_disable.ui_value = '1'
305 dotencode_disable.ui_value = '1'
306
306
307 try:
307 try:
308 self.sa.add(hooks1)
308 self.sa.add(hooks1)
309 self.sa.add(hooks2)
309 self.sa.add(hooks2)
310 self.sa.add(hooks3)
310 self.sa.add(hooks3)
311 self.sa.add(hooks4)
311 self.sa.add(hooks4)
312 self.sa.add(dotencode_disable)
312 self.sa.add(dotencode_disable)
313 self.sa.commit()
313 self.sa.commit()
314 except:
314 except:
315 self.sa.rollback()
315 self.sa.rollback()
316 raise
316 raise
317
317
318 def create_ldap_options(self,skip_existing=False):
318 def create_ldap_options(self,skip_existing=False):
319 """Creates ldap settings"""
319 """Creates ldap settings"""
320
320
321 try:
321 try:
322 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
322 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
323 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
323 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
324 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
324 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
325 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
325 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
326 ('ldap_filter', ''), ('ldap_search_scope', ''),
326 ('ldap_filter', ''), ('ldap_search_scope', ''),
327 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
327 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
328 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
328 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
329
329
330 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
330 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
331 log.debug('Skipping option %s' % k)
331 log.debug('Skipping option %s' % k)
332 continue
332 continue
333 setting = RhodeCodeSetting(k, v)
333 setting = RhodeCodeSetting(k, v)
334 self.sa.add(setting)
334 self.sa.add(setting)
335 self.sa.commit()
335 self.sa.commit()
336 except:
336 except:
337 self.sa.rollback()
337 self.sa.rollback()
338 raise
338 raise
339
339
340 def config_prompt(self, test_repo_path='', retries=3):
340 def config_prompt(self, test_repo_path='', retries=3):
341 if retries == 3:
341 if retries == 3:
342 log.info('Setting up repositories config')
342 log.info('Setting up repositories config')
343
343
344 if not self.tests and not test_repo_path:
344 if not self.tests and not test_repo_path:
345 path = raw_input('Specify valid full path to your repositories'
345 path = raw_input('Specify valid full path to your repositories'
346 ' you can change this later in application settings:')
346 ' you can change this later in application settings:')
347 else:
347 else:
348 path = test_repo_path
348 path = test_repo_path
349 path_ok = True
349 path_ok = True
350
350
351 #check proper dir
351 #check proper dir
352 if not os.path.isdir(path):
352 if not os.path.isdir(path):
353 path_ok = False
353 path_ok = False
354 log.error('Given path %s is not a valid directory', path)
354 log.error('Given path %s is not a valid directory', path)
355
355
356 #check write access
356 #check write access
357 if not os.access(path, os.W_OK) and path_ok:
357 if not os.access(path, os.W_OK) and path_ok:
358 path_ok = False
358 path_ok = False
359 log.error('No write permission to given path %s', path)
359 log.error('No write permission to given path %s', path)
360
360
361
361
362 if retries == 0:
362 if retries == 0:
363 sys.exit('max retries reached')
363 sys.exit('max retries reached')
364 if path_ok is False:
364 if path_ok is False:
365 retries -= 1
365 retries -= 1
366 return self.config_prompt(test_repo_path, retries)
366 return self.config_prompt(test_repo_path, retries)
367
367
368 return path
368 return path
369
369
370 def create_settings(self, path):
370 def create_settings(self, path):
371
371
372 self.create_ui_settings()
372 self.create_ui_settings()
373
373
374 #HG UI OPTIONS
374 #HG UI OPTIONS
375 web1 = RhodeCodeUi()
375 web1 = RhodeCodeUi()
376 web1.ui_section = 'web'
376 web1.ui_section = 'web'
377 web1.ui_key = 'push_ssl'
377 web1.ui_key = 'push_ssl'
378 web1.ui_value = 'false'
378 web1.ui_value = 'false'
379
379
380 web2 = RhodeCodeUi()
380 web2 = RhodeCodeUi()
381 web2.ui_section = 'web'
381 web2.ui_section = 'web'
382 web2.ui_key = 'allow_archive'
382 web2.ui_key = 'allow_archive'
383 web2.ui_value = 'gz zip bz2'
383 web2.ui_value = 'gz zip bz2'
384
384
385 web3 = RhodeCodeUi()
385 web3 = RhodeCodeUi()
386 web3.ui_section = 'web'
386 web3.ui_section = 'web'
387 web3.ui_key = 'allow_push'
387 web3.ui_key = 'allow_push'
388 web3.ui_value = '*'
388 web3.ui_value = '*'
389
389
390 web4 = RhodeCodeUi()
390 web4 = RhodeCodeUi()
391 web4.ui_section = 'web'
391 web4.ui_section = 'web'
392 web4.ui_key = 'baseurl'
392 web4.ui_key = 'baseurl'
393 web4.ui_value = '/'
393 web4.ui_value = '/'
394
394
395 paths = RhodeCodeUi()
395 paths = RhodeCodeUi()
396 paths.ui_section = 'paths'
396 paths.ui_section = 'paths'
397 paths.ui_key = '/'
397 paths.ui_key = '/'
398 paths.ui_value = path
398 paths.ui_value = path
399
399
400 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
400 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
401 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
401 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
402 hgsettings3 = RhodeCodeSetting('ga_code', '')
402 hgsettings3 = RhodeCodeSetting('ga_code', '')
403
403
404 try:
404 try:
405 self.sa.add(web1)
405 self.sa.add(web1)
406 self.sa.add(web2)
406 self.sa.add(web2)
407 self.sa.add(web3)
407 self.sa.add(web3)
408 self.sa.add(web4)
408 self.sa.add(web4)
409 self.sa.add(paths)
409 self.sa.add(paths)
410 self.sa.add(hgsettings1)
410 self.sa.add(hgsettings1)
411 self.sa.add(hgsettings2)
411 self.sa.add(hgsettings2)
412 self.sa.add(hgsettings3)
412 self.sa.add(hgsettings3)
413
413
414 self.sa.commit()
414 self.sa.commit()
415 except:
415 except:
416 self.sa.rollback()
416 self.sa.rollback()
417 raise
417 raise
418
418
419 self.create_ldap_options()
419 self.create_ldap_options()
420
420
421 log.info('created ui config')
421 log.info('created ui config')
422
422
423 def create_user(self, username, password, email='', admin=False):
423 def create_user(self, username, password, email='', admin=False):
424 log.info('creating user %s', username)
424 log.info('creating user %s', username)
425 UserModel().create_or_update(username, password, email,
425 UserModel().create_or_update(username, password, email,
426 name='RhodeCode', lastname='Admin',
426 name='RhodeCode', lastname='Admin',
427 active=True, admin=admin)
427 active=True, admin=admin)
428
428
429 def create_default_user(self):
429 def create_default_user(self):
430 log.info('creating default user')
430 log.info('creating default user')
431 # create default user for handling default permissions.
431 # create default user for handling default permissions.
432 UserModel().create_or_update(username='default',
432 UserModel().create_or_update(username='default',
433 password=str(uuid.uuid1())[:8],
433 password=str(uuid.uuid1())[:8],
434 email='anonymous@rhodecode.org',
434 email='anonymous@rhodecode.org',
435 name='Anonymous', lastname='User')
435 name='Anonymous', lastname='User')
436
436
437 def create_permissions(self):
437 def create_permissions(self):
438 #module.(access|create|change|delete)_[name]
438 #module.(access|create|change|delete)_[name]
439 #module.(read|write|owner)
439 #module.(read|write|owner)
440 perms = [('repository.none', 'Repository no access'),
440 perms = [('repository.none', 'Repository no access'),
441 ('repository.read', 'Repository read access'),
441 ('repository.read', 'Repository read access'),
442 ('repository.write', 'Repository write access'),
442 ('repository.write', 'Repository write access'),
443 ('repository.admin', 'Repository admin access'),
443 ('repository.admin', 'Repository admin access'),
444 ('hg.admin', 'Hg Administrator'),
444 ('hg.admin', 'Hg Administrator'),
445 ('hg.create.repository', 'Repository create'),
445 ('hg.create.repository', 'Repository create'),
446 ('hg.create.none', 'Repository creation disabled'),
446 ('hg.create.none', 'Repository creation disabled'),
447 ('hg.register.none', 'Register disabled'),
447 ('hg.register.none', 'Register disabled'),
448 ('hg.register.manual_activate', 'Register new user with '
448 ('hg.register.manual_activate', 'Register new user with '
449 'RhodeCode without manual'
449 'RhodeCode without manual'
450 'activation'),
450 'activation'),
451
451
452 ('hg.register.auto_activate', 'Register new user with '
452 ('hg.register.auto_activate', 'Register new user with '
453 'RhodeCode without auto '
453 'RhodeCode without auto '
454 'activation'),
454 'activation'),
455 ]
455 ]
456
456
457 for p in perms:
457 for p in perms:
458 new_perm = Permission()
458 new_perm = Permission()
459 new_perm.permission_name = p[0]
459 new_perm.permission_name = p[0]
460 new_perm.permission_longname = p[1]
460 new_perm.permission_longname = p[1]
461 try:
461 try:
462 self.sa.add(new_perm)
462 self.sa.add(new_perm)
463 self.sa.commit()
463 self.sa.commit()
464 except:
464 except:
465 self.sa.rollback()
465 self.sa.rollback()
466 raise
466 raise
467
467
468 def populate_default_permissions(self):
468 def populate_default_permissions(self):
469 log.info('creating default user permissions')
469 log.info('creating default user permissions')
470
470
471 default_user = self.sa.query(User)\
471 default_user = self.sa.query(User)\
472 .filter(User.username == 'default').scalar()
472 .filter(User.username == 'default').scalar()
473
473
474 reg_perm = UserToPerm()
474 reg_perm = UserToPerm()
475 reg_perm.user = default_user
475 reg_perm.user = default_user
476 reg_perm.permission = self.sa.query(Permission)\
476 reg_perm.permission = self.sa.query(Permission)\
477 .filter(Permission.permission_name == 'hg.register.manual_activate')\
477 .filter(Permission.permission_name == 'hg.register.manual_activate')\
478 .scalar()
478 .scalar()
479
479
480 create_repo_perm = UserToPerm()
480 create_repo_perm = UserToPerm()
481 create_repo_perm.user = default_user
481 create_repo_perm.user = default_user
482 create_repo_perm.permission = self.sa.query(Permission)\
482 create_repo_perm.permission = self.sa.query(Permission)\
483 .filter(Permission.permission_name == 'hg.create.repository')\
483 .filter(Permission.permission_name == 'hg.create.repository')\
484 .scalar()
484 .scalar()
485
485
486 default_repo_perm = UserToPerm()
486 default_repo_perm = UserToPerm()
487 default_repo_perm.user = default_user
487 default_repo_perm.user = default_user
488 default_repo_perm.permission = self.sa.query(Permission)\
488 default_repo_perm.permission = self.sa.query(Permission)\
489 .filter(Permission.permission_name == 'repository.read')\
489 .filter(Permission.permission_name == 'repository.read')\
490 .scalar()
490 .scalar()
491
491
492 try:
492 try:
493 self.sa.add(reg_perm)
493 self.sa.add(reg_perm)
494 self.sa.add(create_repo_perm)
494 self.sa.add(create_repo_perm)
495 self.sa.add(default_repo_perm)
495 self.sa.add(default_repo_perm)
496 self.sa.commit()
496 self.sa.commit()
497 except:
497 except:
498 self.sa.rollback()
498 self.sa.rollback()
499 raise
499 raise
@@ -1,675 +1,675 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11
11
12 from datetime import datetime
12 from datetime import datetime
13 from pygments.formatters import HtmlFormatter
13 from pygments.formatters.html import HtmlFormatter
14 from pygments import highlight as code_highlight
14 from pygments import highlight as code_highlight
15 from pylons import url, request, config
15 from pylons import url, request, config
16 from pylons.i18n.translation import _, ungettext
16 from pylons.i18n.translation import _, ungettext
17
17
18 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html import literal, HTML, escape
19 from webhelpers.html.tools import *
19 from webhelpers.html.tools import *
20 from webhelpers.html.builder import make_tag
20 from webhelpers.html.builder import make_tag
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 password, textarea, title, ul, xml_declaration, radio
24 password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 mail_to, strip_links, strip_tags, tag_re
26 mail_to, strip_links, strip_tags, tag_re
27 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.number import format_byte_size, format_bit_size
28 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib import Flash as _Flash
29 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.pylonslib.secure_form import secure_form
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
32 replace_whitespace, urlify, truncate, wrap_paragraphs
32 replace_whitespace, urlify, truncate, wrap_paragraphs
33 from webhelpers.date import time_ago_in_words
33 from webhelpers.date import time_ago_in_words
34 from webhelpers.paginate import Page
34 from webhelpers.paginate import Page
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 convert_boolean_attrs, NotGiven, _make_safe_id_component
36 convert_boolean_attrs, NotGiven, _make_safe_id_component
37
37
38 from vcs.utils.annotate import annotate_highlight
38 from vcs.utils.annotate import annotate_highlight
39 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
40 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
41
41
42 from rhodecode.lib.markup_renderer import MarkupRenderer
42 from rhodecode.lib.markup_renderer import MarkupRenderer
43
43
44 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
44 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
45 """
45 """
46 Reset button
46 Reset button
47 """
47 """
48 _set_input_attrs(attrs, type, name, value)
48 _set_input_attrs(attrs, type, name, value)
49 _set_id_attr(attrs, id, name)
49 _set_id_attr(attrs, id, name)
50 convert_boolean_attrs(attrs, ["disabled"])
50 convert_boolean_attrs(attrs, ["disabled"])
51 return HTML.input(**attrs)
51 return HTML.input(**attrs)
52
52
53 reset = _reset
53 reset = _reset
54 safeid = _make_safe_id_component
54 safeid = _make_safe_id_component
55
55
56 def get_token():
56 def get_token():
57 """Return the current authentication token, creating one if one doesn't
57 """Return the current authentication token, creating one if one doesn't
58 already exist.
58 already exist.
59 """
59 """
60 token_key = "_authentication_token"
60 token_key = "_authentication_token"
61 from pylons import session
61 from pylons import session
62 if not token_key in session:
62 if not token_key in session:
63 try:
63 try:
64 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
64 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
65 except AttributeError: # Python < 2.4
65 except AttributeError: # Python < 2.4
66 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
66 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
67 session[token_key] = token
67 session[token_key] = token
68 if hasattr(session, 'save'):
68 if hasattr(session, 'save'):
69 session.save()
69 session.save()
70 return session[token_key]
70 return session[token_key]
71
71
72 class _GetError(object):
72 class _GetError(object):
73 """Get error from form_errors, and represent it as span wrapped error
73 """Get error from form_errors, and represent it as span wrapped error
74 message
74 message
75
75
76 :param field_name: field to fetch errors for
76 :param field_name: field to fetch errors for
77 :param form_errors: form errors dict
77 :param form_errors: form errors dict
78 """
78 """
79
79
80 def __call__(self, field_name, form_errors):
80 def __call__(self, field_name, form_errors):
81 tmpl = """<span class="error_msg">%s</span>"""
81 tmpl = """<span class="error_msg">%s</span>"""
82 if form_errors and form_errors.has_key(field_name):
82 if form_errors and form_errors.has_key(field_name):
83 return literal(tmpl % form_errors.get(field_name))
83 return literal(tmpl % form_errors.get(field_name))
84
84
85 get_error = _GetError()
85 get_error = _GetError()
86
86
87 class _ToolTip(object):
87 class _ToolTip(object):
88
88
89 def __call__(self, tooltip_title, trim_at=50):
89 def __call__(self, tooltip_title, trim_at=50):
90 """Special function just to wrap our text into nice formatted
90 """Special function just to wrap our text into nice formatted
91 autowrapped text
91 autowrapped text
92
92
93 :param tooltip_title:
93 :param tooltip_title:
94 """
94 """
95 return escape(tooltip_title)
95 return escape(tooltip_title)
96 tooltip = _ToolTip()
96 tooltip = _ToolTip()
97
97
98 class _FilesBreadCrumbs(object):
98 class _FilesBreadCrumbs(object):
99
99
100 def __call__(self, repo_name, rev, paths):
100 def __call__(self, repo_name, rev, paths):
101 if isinstance(paths, str):
101 if isinstance(paths, str):
102 paths = safe_unicode(paths)
102 paths = safe_unicode(paths)
103 url_l = [link_to(repo_name, url('files_home',
103 url_l = [link_to(repo_name, url('files_home',
104 repo_name=repo_name,
104 repo_name=repo_name,
105 revision=rev, f_path=''))]
105 revision=rev, f_path=''))]
106 paths_l = paths.split('/')
106 paths_l = paths.split('/')
107 for cnt, p in enumerate(paths_l):
107 for cnt, p in enumerate(paths_l):
108 if p != '':
108 if p != '':
109 url_l.append(link_to(p, url('files_home',
109 url_l.append(link_to(p, url('files_home',
110 repo_name=repo_name,
110 repo_name=repo_name,
111 revision=rev,
111 revision=rev,
112 f_path='/'.join(paths_l[:cnt + 1]))))
112 f_path='/'.join(paths_l[:cnt + 1]))))
113
113
114 return literal('/'.join(url_l))
114 return literal('/'.join(url_l))
115
115
116 files_breadcrumbs = _FilesBreadCrumbs()
116 files_breadcrumbs = _FilesBreadCrumbs()
117
117
118 class CodeHtmlFormatter(HtmlFormatter):
118 class CodeHtmlFormatter(HtmlFormatter):
119 """My code Html Formatter for source codes
119 """My code Html Formatter for source codes
120 """
120 """
121
121
122 def wrap(self, source, outfile):
122 def wrap(self, source, outfile):
123 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
123 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
124
124
125 def _wrap_code(self, source):
125 def _wrap_code(self, source):
126 for cnt, it in enumerate(source):
126 for cnt, it in enumerate(source):
127 i, t = it
127 i, t = it
128 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
128 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
129 yield i, t
129 yield i, t
130
130
131 def _wrap_tablelinenos(self, inner):
131 def _wrap_tablelinenos(self, inner):
132 dummyoutfile = StringIO.StringIO()
132 dummyoutfile = StringIO.StringIO()
133 lncount = 0
133 lncount = 0
134 for t, line in inner:
134 for t, line in inner:
135 if t:
135 if t:
136 lncount += 1
136 lncount += 1
137 dummyoutfile.write(line)
137 dummyoutfile.write(line)
138
138
139 fl = self.linenostart
139 fl = self.linenostart
140 mw = len(str(lncount + fl - 1))
140 mw = len(str(lncount + fl - 1))
141 sp = self.linenospecial
141 sp = self.linenospecial
142 st = self.linenostep
142 st = self.linenostep
143 la = self.lineanchors
143 la = self.lineanchors
144 aln = self.anchorlinenos
144 aln = self.anchorlinenos
145 nocls = self.noclasses
145 nocls = self.noclasses
146 if sp:
146 if sp:
147 lines = []
147 lines = []
148
148
149 for i in range(fl, fl + lncount):
149 for i in range(fl, fl + lncount):
150 if i % st == 0:
150 if i % st == 0:
151 if i % sp == 0:
151 if i % sp == 0:
152 if aln:
152 if aln:
153 lines.append('<a href="#%s%d" class="special">%*d</a>' %
153 lines.append('<a href="#%s%d" class="special">%*d</a>' %
154 (la, i, mw, i))
154 (la, i, mw, i))
155 else:
155 else:
156 lines.append('<span class="special">%*d</span>' % (mw, i))
156 lines.append('<span class="special">%*d</span>' % (mw, i))
157 else:
157 else:
158 if aln:
158 if aln:
159 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
159 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
160 else:
160 else:
161 lines.append('%*d' % (mw, i))
161 lines.append('%*d' % (mw, i))
162 else:
162 else:
163 lines.append('')
163 lines.append('')
164 ls = '\n'.join(lines)
164 ls = '\n'.join(lines)
165 else:
165 else:
166 lines = []
166 lines = []
167 for i in range(fl, fl + lncount):
167 for i in range(fl, fl + lncount):
168 if i % st == 0:
168 if i % st == 0:
169 if aln:
169 if aln:
170 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
170 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
171 else:
171 else:
172 lines.append('%*d' % (mw, i))
172 lines.append('%*d' % (mw, i))
173 else:
173 else:
174 lines.append('')
174 lines.append('')
175 ls = '\n'.join(lines)
175 ls = '\n'.join(lines)
176
176
177 # in case you wonder about the seemingly redundant <div> here: since the
177 # in case you wonder about the seemingly redundant <div> here: since the
178 # content in the other cell also is wrapped in a div, some browsers in
178 # content in the other cell also is wrapped in a div, some browsers in
179 # some configurations seem to mess up the formatting...
179 # some configurations seem to mess up the formatting...
180 if nocls:
180 if nocls:
181 yield 0, ('<table class="%stable">' % self.cssclass +
181 yield 0, ('<table class="%stable">' % self.cssclass +
182 '<tr><td><div class="linenodiv" '
182 '<tr><td><div class="linenodiv" '
183 'style="background-color: #f0f0f0; padding-right: 10px">'
183 'style="background-color: #f0f0f0; padding-right: 10px">'
184 '<pre style="line-height: 125%">' +
184 '<pre style="line-height: 125%">' +
185 ls + '</pre></div></td><td id="hlcode" class="code">')
185 ls + '</pre></div></td><td id="hlcode" class="code">')
186 else:
186 else:
187 yield 0, ('<table class="%stable">' % self.cssclass +
187 yield 0, ('<table class="%stable">' % self.cssclass +
188 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
188 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
189 ls + '</pre></div></td><td id="hlcode" class="code">')
189 ls + '</pre></div></td><td id="hlcode" class="code">')
190 yield 0, dummyoutfile.getvalue()
190 yield 0, dummyoutfile.getvalue()
191 yield 0, '</td></tr></table>'
191 yield 0, '</td></tr></table>'
192
192
193
193
194 def pygmentize(filenode, **kwargs):
194 def pygmentize(filenode, **kwargs):
195 """pygmentize function using pygments
195 """pygmentize function using pygments
196
196
197 :param filenode:
197 :param filenode:
198 """
198 """
199
199
200 return literal(code_highlight(filenode.content,
200 return literal(code_highlight(filenode.content,
201 filenode.lexer, CodeHtmlFormatter(**kwargs)))
201 filenode.lexer, CodeHtmlFormatter(**kwargs)))
202
202
203 def pygmentize_annotation(repo_name, filenode, **kwargs):
203 def pygmentize_annotation(repo_name, filenode, **kwargs):
204 """pygmentize function for annotation
204 """pygmentize function for annotation
205
205
206 :param filenode:
206 :param filenode:
207 """
207 """
208
208
209 color_dict = {}
209 color_dict = {}
210 def gen_color(n=10000):
210 def gen_color(n=10000):
211 """generator for getting n of evenly distributed colors using
211 """generator for getting n of evenly distributed colors using
212 hsv color and golden ratio. It always return same order of colors
212 hsv color and golden ratio. It always return same order of colors
213
213
214 :returns: RGB tuple
214 :returns: RGB tuple
215 """
215 """
216
216
217 def hsv_to_rgb(h, s, v):
217 def hsv_to_rgb(h, s, v):
218 if s == 0.0: return v, v, v
218 if s == 0.0: return v, v, v
219 i = int(h * 6.0) # XXX assume int() truncates!
219 i = int(h * 6.0) # XXX assume int() truncates!
220 f = (h * 6.0) - i
220 f = (h * 6.0) - i
221 p = v * (1.0 - s)
221 p = v * (1.0 - s)
222 q = v * (1.0 - s * f)
222 q = v * (1.0 - s * f)
223 t = v * (1.0 - s * (1.0 - f))
223 t = v * (1.0 - s * (1.0 - f))
224 i = i % 6
224 i = i % 6
225 if i == 0: return v, t, p
225 if i == 0: return v, t, p
226 if i == 1: return q, v, p
226 if i == 1: return q, v, p
227 if i == 2: return p, v, t
227 if i == 2: return p, v, t
228 if i == 3: return p, q, v
228 if i == 3: return p, q, v
229 if i == 4: return t, p, v
229 if i == 4: return t, p, v
230 if i == 5: return v, p, q
230 if i == 5: return v, p, q
231
231
232 golden_ratio = 0.618033988749895
232 golden_ratio = 0.618033988749895
233 h = 0.22717784590367374
233 h = 0.22717784590367374
234
234
235 for _ in xrange(n):
235 for _ in xrange(n):
236 h += golden_ratio
236 h += golden_ratio
237 h %= 1
237 h %= 1
238 HSV_tuple = [h, 0.95, 0.95]
238 HSV_tuple = [h, 0.95, 0.95]
239 RGB_tuple = hsv_to_rgb(*HSV_tuple)
239 RGB_tuple = hsv_to_rgb(*HSV_tuple)
240 yield map(lambda x:str(int(x * 256)), RGB_tuple)
240 yield map(lambda x:str(int(x * 256)), RGB_tuple)
241
241
242 cgenerator = gen_color()
242 cgenerator = gen_color()
243
243
244 def get_color_string(cs):
244 def get_color_string(cs):
245 if color_dict.has_key(cs):
245 if color_dict.has_key(cs):
246 col = color_dict[cs]
246 col = color_dict[cs]
247 else:
247 else:
248 col = color_dict[cs] = cgenerator.next()
248 col = color_dict[cs] = cgenerator.next()
249 return "color: rgb(%s)! important;" % (', '.join(col))
249 return "color: rgb(%s)! important;" % (', '.join(col))
250
250
251 def url_func(repo_name):
251 def url_func(repo_name):
252
252
253 def _url_func(changeset):
253 def _url_func(changeset):
254 author = changeset.author
254 author = changeset.author
255 date = changeset.date
255 date = changeset.date
256 message = tooltip(changeset.message)
256 message = tooltip(changeset.message)
257
257
258 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
258 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
259 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
259 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
260 "</b> %s<br/></div>")
260 "</b> %s<br/></div>")
261
261
262 tooltip_html = tooltip_html % (author, date, message)
262 tooltip_html = tooltip_html % (author, date, message)
263 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
263 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
264 short_id(changeset.raw_id))
264 short_id(changeset.raw_id))
265 uri = link_to(
265 uri = link_to(
266 lnk_format,
266 lnk_format,
267 url('changeset_home', repo_name=repo_name,
267 url('changeset_home', repo_name=repo_name,
268 revision=changeset.raw_id),
268 revision=changeset.raw_id),
269 style=get_color_string(changeset.raw_id),
269 style=get_color_string(changeset.raw_id),
270 class_='tooltip',
270 class_='tooltip',
271 title=tooltip_html
271 title=tooltip_html
272 )
272 )
273
273
274 uri += '\n'
274 uri += '\n'
275 return uri
275 return uri
276 return _url_func
276 return _url_func
277
277
278 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
278 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
279
279
280 def is_following_repo(repo_name, user_id):
280 def is_following_repo(repo_name, user_id):
281 from rhodecode.model.scm import ScmModel
281 from rhodecode.model.scm import ScmModel
282 return ScmModel().is_following_repo(repo_name, user_id)
282 return ScmModel().is_following_repo(repo_name, user_id)
283
283
284 flash = _Flash()
284 flash = _Flash()
285
285
286 #==============================================================================
286 #==============================================================================
287 # SCM FILTERS available via h.
287 # SCM FILTERS available via h.
288 #==============================================================================
288 #==============================================================================
289 from vcs.utils import author_name, author_email
289 from vcs.utils import author_name, author_email
290 from rhodecode.lib import credentials_filter, age as _age
290 from rhodecode.lib import credentials_filter, age as _age
291
291
292 age = lambda x:_age(x)
292 age = lambda x:_age(x)
293 capitalize = lambda x: x.capitalize()
293 capitalize = lambda x: x.capitalize()
294 email = author_email
294 email = author_email
295 email_or_none = lambda x: email(x) if email(x) != x else None
295 email_or_none = lambda x: email(x) if email(x) != x else None
296 person = lambda x: author_name(x)
296 person = lambda x: author_name(x)
297 short_id = lambda x: x[:12]
297 short_id = lambda x: x[:12]
298 hide_credentials = lambda x: ''.join(credentials_filter(x))
298 hide_credentials = lambda x: ''.join(credentials_filter(x))
299
299
300 def bool2icon(value):
300 def bool2icon(value):
301 """Returns True/False values represented as small html image of true/false
301 """Returns True/False values represented as small html image of true/false
302 icons
302 icons
303
303
304 :param value: bool value
304 :param value: bool value
305 """
305 """
306
306
307 if value is True:
307 if value is True:
308 return HTML.tag('img', src=url("/images/icons/accept.png"),
308 return HTML.tag('img', src=url("/images/icons/accept.png"),
309 alt=_('True'))
309 alt=_('True'))
310
310
311 if value is False:
311 if value is False:
312 return HTML.tag('img', src=url("/images/icons/cancel.png"),
312 return HTML.tag('img', src=url("/images/icons/cancel.png"),
313 alt=_('False'))
313 alt=_('False'))
314
314
315 return value
315 return value
316
316
317
317
318 def action_parser(user_log, feed=False):
318 def action_parser(user_log, feed=False):
319 """This helper will action_map the specified string action into translated
319 """This helper will action_map the specified string action into translated
320 fancy names with icons and links
320 fancy names with icons and links
321
321
322 :param user_log: user log instance
322 :param user_log: user log instance
323 :param feed: use output for feeds (no html and fancy icons)
323 :param feed: use output for feeds (no html and fancy icons)
324 """
324 """
325
325
326 action = user_log.action
326 action = user_log.action
327 action_params = ' '
327 action_params = ' '
328
328
329 x = action.split(':')
329 x = action.split(':')
330
330
331 if len(x) > 1:
331 if len(x) > 1:
332 action, action_params = x
332 action, action_params = x
333
333
334 def get_cs_links():
334 def get_cs_links():
335 revs_limit = 3 #display this amount always
335 revs_limit = 3 #display this amount always
336 revs_top_limit = 50 #show upto this amount of changesets hidden
336 revs_top_limit = 50 #show upto this amount of changesets hidden
337 revs = action_params.split(',')
337 revs = action_params.split(',')
338 repo_name = user_log.repository.repo_name
338 repo_name = user_log.repository.repo_name
339
339
340 from rhodecode.model.scm import ScmModel
340 from rhodecode.model.scm import ScmModel
341 repo = user_log.repository.scm_instance
341 repo = user_log.repository.scm_instance
342
342
343 message = lambda rev: get_changeset_safe(repo, rev).message
343 message = lambda rev: get_changeset_safe(repo, rev).message
344 cs_links = []
344 cs_links = []
345 cs_links.append(" " + ', '.join ([link_to(rev,
345 cs_links.append(" " + ', '.join ([link_to(rev,
346 url('changeset_home',
346 url('changeset_home',
347 repo_name=repo_name,
347 repo_name=repo_name,
348 revision=rev), title=tooltip(message(rev)),
348 revision=rev), title=tooltip(message(rev)),
349 class_='tooltip') for rev in revs[:revs_limit] ]))
349 class_='tooltip') for rev in revs[:revs_limit] ]))
350
350
351 compare_view = (' <div class="compare_view tooltip" title="%s">'
351 compare_view = (' <div class="compare_view tooltip" title="%s">'
352 '<a href="%s">%s</a> '
352 '<a href="%s">%s</a> '
353 '</div>' % (_('Show all combined changesets %s->%s' \
353 '</div>' % (_('Show all combined changesets %s->%s' \
354 % (revs[0], revs[-1])),
354 % (revs[0], revs[-1])),
355 url('changeset_home', repo_name=repo_name,
355 url('changeset_home', repo_name=repo_name,
356 revision='%s...%s' % (revs[0], revs[-1])
356 revision='%s...%s' % (revs[0], revs[-1])
357 ),
357 ),
358 _('compare view'))
358 _('compare view'))
359 )
359 )
360
360
361 if len(revs) > revs_limit:
361 if len(revs) > revs_limit:
362 uniq_id = revs[0]
362 uniq_id = revs[0]
363 html_tmpl = ('<span> %s '
363 html_tmpl = ('<span> %s '
364 '<a class="show_more" id="_%s" href="#more">%s</a> '
364 '<a class="show_more" id="_%s" href="#more">%s</a> '
365 '%s</span>')
365 '%s</span>')
366 if not feed:
366 if not feed:
367 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
367 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
368 % (len(revs) - revs_limit),
368 % (len(revs) - revs_limit),
369 _('revisions')))
369 _('revisions')))
370
370
371 if not feed:
371 if not feed:
372 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
372 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
373 else:
373 else:
374 html_tmpl = '<span id="%s"> %s </span>'
374 html_tmpl = '<span id="%s"> %s </span>'
375
375
376 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
376 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
377 url('changeset_home',
377 url('changeset_home',
378 repo_name=repo_name, revision=rev),
378 repo_name=repo_name, revision=rev),
379 title=message(rev), class_='tooltip')
379 title=message(rev), class_='tooltip')
380 for rev in revs[revs_limit:revs_top_limit]])))
380 for rev in revs[revs_limit:revs_top_limit]])))
381 if len(revs) > 1:
381 if len(revs) > 1:
382 cs_links.append(compare_view)
382 cs_links.append(compare_view)
383 return ''.join(cs_links)
383 return ''.join(cs_links)
384
384
385 def get_fork_name():
385 def get_fork_name():
386 repo_name = action_params
386 repo_name = action_params
387 return _('fork name ') + str(link_to(action_params, url('summary_home',
387 return _('fork name ') + str(link_to(action_params, url('summary_home',
388 repo_name=repo_name,)))
388 repo_name=repo_name,)))
389
389
390 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
390 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
391 'user_created_repo':(_('[created] repository'), None),
391 'user_created_repo':(_('[created] repository'), None),
392 'user_forked_repo':(_('[forked] repository'), get_fork_name),
392 'user_forked_repo':(_('[forked] repository'), get_fork_name),
393 'user_updated_repo':(_('[updated] repository'), None),
393 'user_updated_repo':(_('[updated] repository'), None),
394 'admin_deleted_repo':(_('[delete] repository'), None),
394 'admin_deleted_repo':(_('[delete] repository'), None),
395 'admin_created_repo':(_('[created] repository'), None),
395 'admin_created_repo':(_('[created] repository'), None),
396 'admin_forked_repo':(_('[forked] repository'), None),
396 'admin_forked_repo':(_('[forked] repository'), None),
397 'admin_updated_repo':(_('[updated] repository'), None),
397 'admin_updated_repo':(_('[updated] repository'), None),
398 'push':(_('[pushed] into'), get_cs_links),
398 'push':(_('[pushed] into'), get_cs_links),
399 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
399 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
400 'push_remote':(_('[pulled from remote] into'), get_cs_links),
400 'push_remote':(_('[pulled from remote] into'), get_cs_links),
401 'pull':(_('[pulled] from'), None),
401 'pull':(_('[pulled] from'), None),
402 'started_following_repo':(_('[started following] repository'), None),
402 'started_following_repo':(_('[started following] repository'), None),
403 'stopped_following_repo':(_('[stopped following] repository'), None),
403 'stopped_following_repo':(_('[stopped following] repository'), None),
404 }
404 }
405
405
406 action_str = action_map.get(action, action)
406 action_str = action_map.get(action, action)
407 if feed:
407 if feed:
408 action = action_str[0].replace('[', '').replace(']', '')
408 action = action_str[0].replace('[', '').replace(']', '')
409 else:
409 else:
410 action = action_str[0].replace('[', '<span class="journal_highlight">')\
410 action = action_str[0].replace('[', '<span class="journal_highlight">')\
411 .replace(']', '</span>')
411 .replace(']', '</span>')
412
412
413 action_params_func = lambda :""
413 action_params_func = lambda :""
414
414
415 if callable(action_str[1]):
415 if callable(action_str[1]):
416 action_params_func = action_str[1]
416 action_params_func = action_str[1]
417
417
418 return [literal(action), action_params_func]
418 return [literal(action), action_params_func]
419
419
420 def action_parser_icon(user_log):
420 def action_parser_icon(user_log):
421 action = user_log.action
421 action = user_log.action
422 action_params = None
422 action_params = None
423 x = action.split(':')
423 x = action.split(':')
424
424
425 if len(x) > 1:
425 if len(x) > 1:
426 action, action_params = x
426 action, action_params = x
427
427
428 tmpl = """<img src="%s%s" alt="%s"/>"""
428 tmpl = """<img src="%s%s" alt="%s"/>"""
429 map = {'user_deleted_repo':'database_delete.png',
429 map = {'user_deleted_repo':'database_delete.png',
430 'user_created_repo':'database_add.png',
430 'user_created_repo':'database_add.png',
431 'user_forked_repo':'arrow_divide.png',
431 'user_forked_repo':'arrow_divide.png',
432 'user_updated_repo':'database_edit.png',
432 'user_updated_repo':'database_edit.png',
433 'admin_deleted_repo':'database_delete.png',
433 'admin_deleted_repo':'database_delete.png',
434 'admin_created_repo':'database_add.png',
434 'admin_created_repo':'database_add.png',
435 'admin_forked_repo':'arrow_divide.png',
435 'admin_forked_repo':'arrow_divide.png',
436 'admin_updated_repo':'database_edit.png',
436 'admin_updated_repo':'database_edit.png',
437 'push':'script_add.png',
437 'push':'script_add.png',
438 'push_local':'script_edit.png',
438 'push_local':'script_edit.png',
439 'push_remote':'connect.png',
439 'push_remote':'connect.png',
440 'pull':'down_16.png',
440 'pull':'down_16.png',
441 'started_following_repo':'heart_add.png',
441 'started_following_repo':'heart_add.png',
442 'stopped_following_repo':'heart_delete.png',
442 'stopped_following_repo':'heart_delete.png',
443 }
443 }
444 return literal(tmpl % ((url('/images/icons/')),
444 return literal(tmpl % ((url('/images/icons/')),
445 map.get(action, action), action))
445 map.get(action, action), action))
446
446
447
447
448 #==============================================================================
448 #==============================================================================
449 # PERMS
449 # PERMS
450 #==============================================================================
450 #==============================================================================
451 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
451 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
452 HasRepoPermissionAny, HasRepoPermissionAll
452 HasRepoPermissionAny, HasRepoPermissionAll
453
453
454 #==============================================================================
454 #==============================================================================
455 # GRAVATAR URL
455 # GRAVATAR URL
456 #==============================================================================
456 #==============================================================================
457
457
458 def gravatar_url(email_address, size=30):
458 def gravatar_url(email_address, size=30):
459 if (not str2bool(config['app_conf'].get('use_gravatar')) or
459 if (not str2bool(config['app_conf'].get('use_gravatar')) or
460 not email_address or email_address == 'anonymous@rhodecode.org'):
460 not email_address or email_address == 'anonymous@rhodecode.org'):
461 return url("/images/user%s.png" % size)
461 return url("/images/user%s.png" % size)
462
462
463 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
463 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
464 default = 'identicon'
464 default = 'identicon'
465 baseurl_nossl = "http://www.gravatar.com/avatar/"
465 baseurl_nossl = "http://www.gravatar.com/avatar/"
466 baseurl_ssl = "https://secure.gravatar.com/avatar/"
466 baseurl_ssl = "https://secure.gravatar.com/avatar/"
467 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
467 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
468
468
469 if isinstance(email_address, unicode):
469 if isinstance(email_address, unicode):
470 #hashlib crashes on unicode items
470 #hashlib crashes on unicode items
471 email_address = safe_str(email_address)
471 email_address = safe_str(email_address)
472 # construct the url
472 # construct the url
473 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
473 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
474 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
474 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
475
475
476 return gravatar_url
476 return gravatar_url
477
477
478
478
479 #==============================================================================
479 #==============================================================================
480 # REPO PAGER, PAGER FOR REPOSITORY
480 # REPO PAGER, PAGER FOR REPOSITORY
481 #==============================================================================
481 #==============================================================================
482 class RepoPage(Page):
482 class RepoPage(Page):
483
483
484 def __init__(self, collection, page=1, items_per_page=20,
484 def __init__(self, collection, page=1, items_per_page=20,
485 item_count=None, url=None, **kwargs):
485 item_count=None, url=None, **kwargs):
486
486
487 """Create a "RepoPage" instance. special pager for paging
487 """Create a "RepoPage" instance. special pager for paging
488 repository
488 repository
489 """
489 """
490 self._url_generator = url
490 self._url_generator = url
491
491
492 # Safe the kwargs class-wide so they can be used in the pager() method
492 # Safe the kwargs class-wide so they can be used in the pager() method
493 self.kwargs = kwargs
493 self.kwargs = kwargs
494
494
495 # Save a reference to the collection
495 # Save a reference to the collection
496 self.original_collection = collection
496 self.original_collection = collection
497
497
498 self.collection = collection
498 self.collection = collection
499
499
500 # The self.page is the number of the current page.
500 # The self.page is the number of the current page.
501 # The first page has the number 1!
501 # The first page has the number 1!
502 try:
502 try:
503 self.page = int(page) # make it int() if we get it as a string
503 self.page = int(page) # make it int() if we get it as a string
504 except (ValueError, TypeError):
504 except (ValueError, TypeError):
505 self.page = 1
505 self.page = 1
506
506
507 self.items_per_page = items_per_page
507 self.items_per_page = items_per_page
508
508
509 # Unless the user tells us how many items the collections has
509 # Unless the user tells us how many items the collections has
510 # we calculate that ourselves.
510 # we calculate that ourselves.
511 if item_count is not None:
511 if item_count is not None:
512 self.item_count = item_count
512 self.item_count = item_count
513 else:
513 else:
514 self.item_count = len(self.collection)
514 self.item_count = len(self.collection)
515
515
516 # Compute the number of the first and last available page
516 # Compute the number of the first and last available page
517 if self.item_count > 0:
517 if self.item_count > 0:
518 self.first_page = 1
518 self.first_page = 1
519 self.page_count = int(math.ceil(float(self.item_count) /
519 self.page_count = int(math.ceil(float(self.item_count) /
520 self.items_per_page))
520 self.items_per_page))
521 self.last_page = self.first_page + self.page_count - 1
521 self.last_page = self.first_page + self.page_count - 1
522
522
523 # Make sure that the requested page number is the range of valid pages
523 # Make sure that the requested page number is the range of valid pages
524 if self.page > self.last_page:
524 if self.page > self.last_page:
525 self.page = self.last_page
525 self.page = self.last_page
526 elif self.page < self.first_page:
526 elif self.page < self.first_page:
527 self.page = self.first_page
527 self.page = self.first_page
528
528
529 # Note: the number of items on this page can be less than
529 # Note: the number of items on this page can be less than
530 # items_per_page if the last page is not full
530 # items_per_page if the last page is not full
531 self.first_item = max(0, (self.item_count) - (self.page *
531 self.first_item = max(0, (self.item_count) - (self.page *
532 items_per_page))
532 items_per_page))
533 self.last_item = ((self.item_count - 1) - items_per_page *
533 self.last_item = ((self.item_count - 1) - items_per_page *
534 (self.page - 1))
534 (self.page - 1))
535
535
536 self.items = list(self.collection[self.first_item:self.last_item + 1])
536 self.items = list(self.collection[self.first_item:self.last_item + 1])
537
537
538
538
539 # Links to previous and next page
539 # Links to previous and next page
540 if self.page > self.first_page:
540 if self.page > self.first_page:
541 self.previous_page = self.page - 1
541 self.previous_page = self.page - 1
542 else:
542 else:
543 self.previous_page = None
543 self.previous_page = None
544
544
545 if self.page < self.last_page:
545 if self.page < self.last_page:
546 self.next_page = self.page + 1
546 self.next_page = self.page + 1
547 else:
547 else:
548 self.next_page = None
548 self.next_page = None
549
549
550 # No items available
550 # No items available
551 else:
551 else:
552 self.first_page = None
552 self.first_page = None
553 self.page_count = 0
553 self.page_count = 0
554 self.last_page = None
554 self.last_page = None
555 self.first_item = None
555 self.first_item = None
556 self.last_item = None
556 self.last_item = None
557 self.previous_page = None
557 self.previous_page = None
558 self.next_page = None
558 self.next_page = None
559 self.items = []
559 self.items = []
560
560
561 # This is a subclass of the 'list' type. Initialise the list now.
561 # This is a subclass of the 'list' type. Initialise the list now.
562 list.__init__(self, reversed(self.items))
562 list.__init__(self, reversed(self.items))
563
563
564
564
565 def changed_tooltip(nodes):
565 def changed_tooltip(nodes):
566 """
566 """
567 Generates a html string for changed nodes in changeset page.
567 Generates a html string for changed nodes in changeset page.
568 It limits the output to 30 entries
568 It limits the output to 30 entries
569
569
570 :param nodes: LazyNodesGenerator
570 :param nodes: LazyNodesGenerator
571 """
571 """
572 if nodes:
572 if nodes:
573 pref = ': <br/> '
573 pref = ': <br/> '
574 suf = ''
574 suf = ''
575 if len(nodes) > 30:
575 if len(nodes) > 30:
576 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
576 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
577 return literal(pref + '<br/> '.join([safe_unicode(x.path)
577 return literal(pref + '<br/> '.join([safe_unicode(x.path)
578 for x in nodes[:30]]) + suf)
578 for x in nodes[:30]]) + suf)
579 else:
579 else:
580 return ': ' + _('No Files')
580 return ': ' + _('No Files')
581
581
582
582
583
583
584 def repo_link(groups_and_repos):
584 def repo_link(groups_and_repos):
585 """
585 """
586 Makes a breadcrumbs link to repo within a group
586 Makes a breadcrumbs link to repo within a group
587 joins &raquo; on each group to create a fancy link
587 joins &raquo; on each group to create a fancy link
588
588
589 ex::
589 ex::
590 group >> subgroup >> repo
590 group >> subgroup >> repo
591
591
592 :param groups_and_repos:
592 :param groups_and_repos:
593 """
593 """
594 groups, repo_name = groups_and_repos
594 groups, repo_name = groups_and_repos
595
595
596 if not groups:
596 if not groups:
597 return repo_name
597 return repo_name
598 else:
598 else:
599 def make_link(group):
599 def make_link(group):
600 return link_to(group.name, url('repos_group_home',
600 return link_to(group.name, url('repos_group_home',
601 group_name=group.group_name))
601 group_name=group.group_name))
602 return literal(' &raquo; '.join(map(make_link, groups)) + \
602 return literal(' &raquo; '.join(map(make_link, groups)) + \
603 " &raquo; " + repo_name)
603 " &raquo; " + repo_name)
604
604
605 def fancy_file_stats(stats):
605 def fancy_file_stats(stats):
606 """
606 """
607 Displays a fancy two colored bar for number of added/deleted
607 Displays a fancy two colored bar for number of added/deleted
608 lines of code on file
608 lines of code on file
609
609
610 :param stats: two element list of added/deleted lines of code
610 :param stats: two element list of added/deleted lines of code
611 """
611 """
612
612
613 a, d, t = stats[0], stats[1], stats[0] + stats[1]
613 a, d, t = stats[0], stats[1], stats[0] + stats[1]
614 width = 100
614 width = 100
615 unit = float(width) / (t or 1)
615 unit = float(width) / (t or 1)
616
616
617 # needs > 9% of width to be visible or 0 to be hidden
617 # needs > 9% of width to be visible or 0 to be hidden
618 a_p = max(9, unit * a) if a > 0 else 0
618 a_p = max(9, unit * a) if a > 0 else 0
619 d_p = max(9, unit * d) if d > 0 else 0
619 d_p = max(9, unit * d) if d > 0 else 0
620 p_sum = a_p + d_p
620 p_sum = a_p + d_p
621
621
622 if p_sum > width:
622 if p_sum > width:
623 #adjust the percentage to be == 100% since we adjusted to 9
623 #adjust the percentage to be == 100% since we adjusted to 9
624 if a_p > d_p:
624 if a_p > d_p:
625 a_p = a_p - (p_sum - width)
625 a_p = a_p - (p_sum - width)
626 else:
626 else:
627 d_p = d_p - (p_sum - width)
627 d_p = d_p - (p_sum - width)
628
628
629 a_v = a if a > 0 else ''
629 a_v = a if a > 0 else ''
630 d_v = d if d > 0 else ''
630 d_v = d if d > 0 else ''
631
631
632
632
633 def cgen(l_type):
633 def cgen(l_type):
634 mapping = {'tr':'top-right-rounded-corner',
634 mapping = {'tr':'top-right-rounded-corner',
635 'tl':'top-left-rounded-corner',
635 'tl':'top-left-rounded-corner',
636 'br':'bottom-right-rounded-corner',
636 'br':'bottom-right-rounded-corner',
637 'bl':'bottom-left-rounded-corner'}
637 'bl':'bottom-left-rounded-corner'}
638 map_getter = lambda x:mapping[x]
638 map_getter = lambda x:mapping[x]
639
639
640 if l_type == 'a' and d_v:
640 if l_type == 'a' and d_v:
641 #case when added and deleted are present
641 #case when added and deleted are present
642 return ' '.join(map(map_getter, ['tl', 'bl']))
642 return ' '.join(map(map_getter, ['tl', 'bl']))
643
643
644 if l_type == 'a' and not d_v:
644 if l_type == 'a' and not d_v:
645 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
645 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
646
646
647 if l_type == 'd' and a_v:
647 if l_type == 'd' and a_v:
648 return ' '.join(map(map_getter, ['tr', 'br']))
648 return ' '.join(map(map_getter, ['tr', 'br']))
649
649
650 if l_type == 'd' and not a_v:
650 if l_type == 'd' and not a_v:
651 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
651 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
652
652
653
653
654
654
655 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
655 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
656 a_p, a_v)
656 a_p, a_v)
657 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
657 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
658 d_p, d_v)
658 d_p, d_v)
659 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
659 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
660
660
661
661
662 def urlify_text(text):
662 def urlify_text(text):
663 import re
663 import re
664
664
665 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
665 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
666
666
667 def url_func(match_obj):
667 def url_func(match_obj):
668 url_full = match_obj.groups()[0]
668 url_full = match_obj.groups()[0]
669 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
669 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
670
670
671 return literal(url_pat.sub(url_func, text))
671 return literal(url_pat.sub(url_func, text))
672
672
673
673
674 def rst(source):
674 def rst(source):
675 return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
675 return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
@@ -1,165 +1,166 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.smtp_mailer
3 rhodecode.lib.smtp_mailer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Simple smtp mailer used in RhodeCode
6 Simple smtp mailer used in RhodeCode
7
7
8 :created_on: Sep 13, 2010
8 :created_on: Sep 13, 2010
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import logging
25 import logging
26 import smtplib
26 import smtplib
27 import mimetypes
27 import mimetypes
28 from socket import sslerror
28 from socket import sslerror
29
29
30 from email.mime.multipart import MIMEMultipart
30 from email.mime.multipart import MIMEMultipart
31 from email.mime.image import MIMEImage
31 from email.mime.image import MIMEImage
32 from email.mime.audio import MIMEAudio
32 from email.mime.audio import MIMEAudio
33 from email.mime.base import MIMEBase
33 from email.mime.base import MIMEBase
34 from email.mime.text import MIMEText
34 from email.mime.text import MIMEText
35 from email.utils import formatdate
35 from email.utils import formatdate
36 from email import encoders
36 from email import encoders
37
37
38
38
39 class SmtpMailer(object):
39 class SmtpMailer(object):
40 """SMTP mailer class
40 """SMTP mailer class
41
41
42 mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
42 mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
43 mail_port, ssl, tls)
43 mail_port, ssl, tls)
44 mailer.send(recipients, subject, body, attachment_files)
44 mailer.send(recipients, subject, body, attachment_files)
45
45
46 :param recipients might be a list of string or single string
46 :param recipients might be a list of string or single string
47 :param attachment_files is a dict of {filename:location}
47 :param attachment_files is a dict of {filename:location}
48 it tries to guess the mimetype and attach the file
48 it tries to guess the mimetype and attach the file
49
49
50 """
50 """
51
51
52 def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
52 def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
53 mail_port=None, ssl=False, tls=False, debug=False):
53 mail_port=None, ssl=False, tls=False, debug=False):
54
54
55 self.mail_from = mail_from
55 self.mail_from = mail_from
56 self.mail_server = mail_server
56 self.mail_server = mail_server
57 self.mail_port = mail_port
57 self.mail_port = mail_port
58 self.user = user
58 self.user = user
59 self.passwd = passwd
59 self.passwd = passwd
60 self.ssl = ssl
60 self.ssl = ssl
61 self.tls = tls
61 self.tls = tls
62 self.debug = debug
62 self.debug = debug
63 self.auth = smtp_auth
63 self.auth = smtp_auth
64
64
65 def send(self, recipients=[], subject='', body='', attachment_files=None):
65 def send(self, recipients=[], subject='', body='', attachment_files=None):
66
66
67 if isinstance(recipients, basestring):
67 if isinstance(recipients, basestring):
68 recipients = [recipients]
68 recipients = [recipients]
69 if self.ssl:
69 if self.ssl:
70 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
70 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
71 else:
71 else:
72 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
72 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
73
73
74 if self.tls:
74 if self.tls:
75 smtp_serv.ehlo()
75 smtp_serv.ehlo()
76 smtp_serv.starttls()
76 smtp_serv.starttls()
77
77
78 if self.debug:
78 if self.debug:
79 smtp_serv.set_debuglevel(1)
79 smtp_serv.set_debuglevel(1)
80
80
81 smtp_serv.ehlo()
81 smtp_serv.ehlo()
82 if self.auth:
82 if self.auth:
83 smtp_serv.esmtp_features["auth"] = self.auth
83 smtp_serv.esmtp_features["auth"] = self.auth
84
84
85 # if server requires authorization you must provide login and password
85 # if server requires authorization you must provide login and password
86 # but only if we have them
86 # but only if we have them
87 if self.user and self.passwd:
87 if self.user and self.passwd:
88 smtp_serv.login(self.user, self.passwd)
88 smtp_serv.login(self.user, self.passwd)
89
89
90 date_ = formatdate(localtime=True)
90 date_ = formatdate(localtime=True)
91 msg = MIMEMultipart()
91 msg = MIMEMultipart()
92 msg.set_type('multipart/alternative')
92 msg.set_type('multipart/alternative')
93 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
93 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
94
94
95 text_msg = MIMEText(body)
95 text_msg = MIMEText(body)
96 text_msg.set_type('text/plain')
96 text_msg.set_type('text/plain')
97 text_msg.set_param('charset', 'UTF-8')
97 text_msg.set_param('charset', 'UTF-8')
98
98
99 msg['From'] = self.mail_from
99 msg['From'] = self.mail_from
100 msg['To'] = ','.join(recipients)
100 msg['To'] = ','.join(recipients)
101 msg['Date'] = date_
101 msg['Date'] = date_
102 msg['Subject'] = subject
102 msg['Subject'] = subject
103
103
104 msg.attach(text_msg)
104 msg.attach(text_msg)
105
105
106 if attachment_files:
106 if attachment_files:
107 self.__atach_files(msg, attachment_files)
107 self.__atach_files(msg, attachment_files)
108
108
109 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
109 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
110 logging.info('MAIL SEND TO: %s' % recipients)
110 logging.info('MAIL SEND TO: %s' % recipients)
111
111
112 try:
112 try:
113 smtp_serv.quit()
113 smtp_serv.quit()
114 except sslerror:
114 except sslerror:
115 # sslerror is raised in tls connections on closing sometimes
115 # sslerror is raised in tls connections on closing sometimes
116 pass
116 pass
117
117
118 def __atach_files(self, msg, attachment_files):
118 def __atach_files(self, msg, attachment_files):
119 if isinstance(attachment_files, dict):
119 if isinstance(attachment_files, dict):
120 for f_name, msg_file in attachment_files.items():
120 for f_name, msg_file in attachment_files.items():
121 ctype, encoding = mimetypes.guess_type(f_name)
121 ctype, encoding = mimetypes.guess_type(f_name)
122 logging.info("guessing file %s type based on %s", ctype,
122 logging.info("guessing file %s type based on %s", ctype,
123 f_name)
123 f_name)
124 if ctype is None or encoding is not None:
124 if ctype is None or encoding is not None:
125 # No guess could be made, or the file is encoded
125 # No guess could be made, or the file is encoded
126 # (compressed), so use a generic bag-of-bits type.
126 # (compressed), so use a generic bag-of-bits type.
127 ctype = 'application/octet-stream'
127 ctype = 'application/octet-stream'
128 maintype, subtype = ctype.split('/', 1)
128 maintype, subtype = ctype.split('/', 1)
129 if maintype == 'text':
129 if maintype == 'text':
130 # Note: we should handle calculating the charset
130 # Note: we should handle calculating the charset
131 file_part = MIMEText(self.get_content(msg_file),
131 file_part = MIMEText(self.get_content(msg_file),
132 _subtype=subtype)
132 _subtype=subtype)
133 elif maintype == 'image':
133 elif maintype == 'image':
134 file_part = MIMEImage(self.get_content(msg_file),
134 file_part = MIMEImage(self.get_content(msg_file),
135 _subtype=subtype)
135 _subtype=subtype)
136 elif maintype == 'audio':
136 elif maintype == 'audio':
137 file_part = MIMEAudio(self.get_content(msg_file),
137 file_part = MIMEAudio(self.get_content(msg_file),
138 _subtype=subtype)
138 _subtype=subtype)
139 else:
139 else:
140 file_part = MIMEBase(maintype, subtype)
140 file_part = MIMEBase(maintype, subtype)
141 file_part.set_payload(self.get_content(msg_file))
141 file_part.set_payload(self.get_content(msg_file))
142 # Encode the payload using Base64
142 # Encode the payload using Base64
143 encoders.encode_base64(msg)
143 encoders.encode_base64(msg)
144 # Set the filename parameter
144 # Set the filename parameter
145 file_part.add_header('Content-Disposition', 'attachment',
145 file_part.add_header('Content-Disposition', 'attachment',
146 filename=f_name)
146 filename=f_name)
147 file_part.add_header('Content-Type', ctype, name=f_name)
147 file_part.add_header('Content-Type', ctype, name=f_name)
148 msg.attach(file_part)
148 msg.attach(file_part)
149 else:
149 else:
150 raise Exception('Attachment files should be'
150 raise Exception('Attachment files should be'
151 'a dict in format {"filename":"filepath"}')
151 'a dict in format {"filename":"filepath"}')
152
152
153 def get_content(self, msg_file):
153 def get_content(self, msg_file):
154 """Get content based on type, if content is a string do open first
154 """
155 Get content based on type, if content is a string do open first
155 else just read because it's a probably open file object
156 else just read because it's a probably open file object
156
157
157 :param msg_file:
158 :param msg_file:
158 """
159 """
159 if isinstance(msg_file, str):
160 if isinstance(msg_file, str):
160 return open(msg_file, "rb").read()
161 return open(msg_file, "rb").read()
161 else:
162 else:
162 # just for safe seek to 0
163 # just for safe seek to 0
163 msg_file.seek(0)
164 msg_file.seek(0)
164 return msg_file.read()
165 return msg_file.read()
165
166
@@ -1,599 +1,594 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import paste
30 import paste
31 import beaker
31 import beaker
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from paste.script.command import Command, BadCommand
34 from paste.script.command import Command, BadCommand
35
35
36 from mercurial import ui, config
36 from mercurial import ui, config
37
37
38 from webhelpers.text import collapse, remove_formatting, strip_tags
38 from webhelpers.text import collapse, remove_formatting, strip_tags
39
39
40 from vcs import get_backend
40 from vcs import get_backend
41 from vcs.backends.base import BaseChangeset
41 from vcs.backends.base import BaseChangeset
42 from vcs.utils.lazy import LazyProperty
42 from vcs.utils.lazy import LazyProperty
43 from vcs.utils.helpers import get_scm
43 from vcs.utils.helpers import get_scm
44 from vcs.exceptions import VCSError
44 from vcs.exceptions import VCSError
45
45
46 from rhodecode.lib.caching_query import FromCache
46 from rhodecode.lib.caching_query import FromCache
47
47
48 from rhodecode.model import meta
48 from rhodecode.model import meta
49 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, RepoGroup, \
49 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, RepoGroup, \
50 RhodeCodeSetting
50 RhodeCodeSetting
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 def recursive_replace(str_, replace=' '):
55 def recursive_replace(str_, replace=' '):
56 """Recursive replace of given sign to just one instance
56 """Recursive replace of given sign to just one instance
57
57
58 :param str_: given string
58 :param str_: given string
59 :param replace: char to find and replace multiple instances
59 :param replace: char to find and replace multiple instances
60
60
61 Examples::
61 Examples::
62 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
62 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
63 'Mighty-Mighty-Bo-sstones'
63 'Mighty-Mighty-Bo-sstones'
64 """
64 """
65
65
66 if str_.find(replace * 2) == -1:
66 if str_.find(replace * 2) == -1:
67 return str_
67 return str_
68 else:
68 else:
69 str_ = str_.replace(replace * 2, replace)
69 str_ = str_.replace(replace * 2, replace)
70 return recursive_replace(str_, replace)
70 return recursive_replace(str_, replace)
71
71
72
72
73 def repo_name_slug(value):
73 def repo_name_slug(value):
74 """Return slug of name of repository
74 """Return slug of name of repository
75 This function is called on each creation/modification
75 This function is called on each creation/modification
76 of repository to prevent bad names in repo
76 of repository to prevent bad names in repo
77 """
77 """
78
78
79 slug = remove_formatting(value)
79 slug = remove_formatting(value)
80 slug = strip_tags(slug)
80 slug = strip_tags(slug)
81
81
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
83 slug = slug.replace(c, '-')
83 slug = slug.replace(c, '-')
84 slug = recursive_replace(slug, '-')
84 slug = recursive_replace(slug, '-')
85 slug = collapse(slug, '-')
85 slug = collapse(slug, '-')
86 return slug
86 return slug
87
87
88
88
89 def get_repo_slug(request):
89 def get_repo_slug(request):
90 return request.environ['pylons.routes_dict'].get('repo_name')
90 return request.environ['pylons.routes_dict'].get('repo_name')
91
91
92
92
93 def action_logger(user, action, repo, ipaddr='', sa=None):
93 def action_logger(user, action, repo, ipaddr='', sa=None):
94 """
94 """
95 Action logger for various actions made by users
95 Action logger for various actions made by users
96
96
97 :param user: user that made this action, can be a unique username string or
97 :param user: user that made this action, can be a unique username string or
98 object containing user_id attribute
98 object containing user_id attribute
99 :param action: action to log, should be on of predefined unique actions for
99 :param action: action to log, should be on of predefined unique actions for
100 easy translations
100 easy translations
101 :param repo: string name of repository or object containing repo_id,
101 :param repo: string name of repository or object containing repo_id,
102 that action was made on
102 that action was made on
103 :param ipaddr: optional ip address from what the action was made
103 :param ipaddr: optional ip address from what the action was made
104 :param sa: optional sqlalchemy session
104 :param sa: optional sqlalchemy session
105
105
106 """
106 """
107
107
108 if not sa:
108 if not sa:
109 sa = meta.Session()
109 sa = meta.Session()
110
110
111 try:
111 try:
112 if hasattr(user, 'user_id'):
112 if hasattr(user, 'user_id'):
113 user_obj = user
113 user_obj = user
114 elif isinstance(user, basestring):
114 elif isinstance(user, basestring):
115 user_obj = User.get_by_username(user)
115 user_obj = User.get_by_username(user)
116 else:
116 else:
117 raise Exception('You have to provide user object or username')
117 raise Exception('You have to provide user object or username')
118
118
119 if hasattr(repo, 'repo_id'):
119 if hasattr(repo, 'repo_id'):
120 repo_obj = Repository.get(repo.repo_id)
120 repo_obj = Repository.get(repo.repo_id)
121 repo_name = repo_obj.repo_name
121 repo_name = repo_obj.repo_name
122 elif isinstance(repo, basestring):
122 elif isinstance(repo, basestring):
123 repo_name = repo.lstrip('/')
123 repo_name = repo.lstrip('/')
124 repo_obj = Repository.get_by_repo_name(repo_name)
124 repo_obj = Repository.get_by_repo_name(repo_name)
125 else:
125 else:
126 raise Exception('You have to provide repository to action logger')
126 raise Exception('You have to provide repository to action logger')
127
127
128 user_log = UserLog()
128 user_log = UserLog()
129 user_log.user_id = user_obj.user_id
129 user_log.user_id = user_obj.user_id
130 user_log.action = action
130 user_log.action = action
131
131
132 user_log.repository_id = repo_obj.repo_id
132 user_log.repository_id = repo_obj.repo_id
133 user_log.repository_name = repo_name
133 user_log.repository_name = repo_name
134
134
135 user_log.action_date = datetime.datetime.now()
135 user_log.action_date = datetime.datetime.now()
136 user_log.user_ip = ipaddr
136 user_log.user_ip = ipaddr
137 sa.add(user_log)
137 sa.add(user_log)
138 sa.commit()
138 sa.commit()
139
139
140 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
140 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
141 except:
141 except:
142 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
143 sa.rollback()
143 sa.rollback()
144
144
145
145
146 def get_repos(path, recursive=False):
146 def get_repos(path, recursive=False):
147 """
147 """
148 Scans given path for repos and return (name,(type,path)) tuple
148 Scans given path for repos and return (name,(type,path)) tuple
149
149
150 :param path: path to scann for repositories
150 :param path: path to scann for repositories
151 :param recursive: recursive search and return names with subdirs in front
151 :param recursive: recursive search and return names with subdirs in front
152 """
152 """
153
153
154 if path.endswith(os.sep):
154 if path.endswith(os.sep):
155 #remove ending slash for better results
155 #remove ending slash for better results
156 path = path[:-1]
156 path = path[:-1]
157
157
158 def _get_repos(p):
158 def _get_repos(p):
159 if not os.access(p, os.W_OK):
159 if not os.access(p, os.W_OK):
160 return
160 return
161 for dirpath in os.listdir(p):
161 for dirpath in os.listdir(p):
162 if os.path.isfile(os.path.join(p, dirpath)):
162 if os.path.isfile(os.path.join(p, dirpath)):
163 continue
163 continue
164 cur_path = os.path.join(p, dirpath)
164 cur_path = os.path.join(p, dirpath)
165 try:
165 try:
166 scm_info = get_scm(cur_path)
166 scm_info = get_scm(cur_path)
167 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
167 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
168 except VCSError:
168 except VCSError:
169 if not recursive:
169 if not recursive:
170 continue
170 continue
171 #check if this dir containts other repos for recursive scan
171 #check if this dir containts other repos for recursive scan
172 rec_path = os.path.join(p, dirpath)
172 rec_path = os.path.join(p, dirpath)
173 if os.path.isdir(rec_path):
173 if os.path.isdir(rec_path):
174 for inner_scm in _get_repos(rec_path):
174 for inner_scm in _get_repos(rec_path):
175 yield inner_scm
175 yield inner_scm
176
176
177 return _get_repos(path)
177 return _get_repos(path)
178
178
179
179
180 def is_valid_repo(repo_name, base_path):
180 def is_valid_repo(repo_name, base_path):
181 """
181 """
182 Returns True if given path is a valid repository False otherwise
182 Returns True if given path is a valid repository False otherwise
183 :param repo_name:
183 :param repo_name:
184 :param base_path:
184 :param base_path:
185
185
186 :return True: if given path is a valid repository
186 :return True: if given path is a valid repository
187 """
187 """
188 full_path = os.path.join(base_path, repo_name)
188 full_path = os.path.join(base_path, repo_name)
189
189
190 try:
190 try:
191 get_scm(full_path)
191 get_scm(full_path)
192 return True
192 return True
193 except VCSError:
193 except VCSError:
194 return False
194 return False
195
195
196 def is_valid_repos_group(repos_group_name, base_path):
196 def is_valid_repos_group(repos_group_name, base_path):
197 """
197 """
198 Returns True if given path is a repos group False otherwise
198 Returns True if given path is a repos group False otherwise
199
199
200 :param repo_name:
200 :param repo_name:
201 :param base_path:
201 :param base_path:
202 """
202 """
203 full_path = os.path.join(base_path, repos_group_name)
203 full_path = os.path.join(base_path, repos_group_name)
204
204
205 # check if it's not a repo
205 # check if it's not a repo
206 if is_valid_repo(repos_group_name, base_path):
206 if is_valid_repo(repos_group_name, base_path):
207 return False
207 return False
208
208
209 # check if it's a valid path
209 # check if it's a valid path
210 if os.path.isdir(full_path):
210 if os.path.isdir(full_path):
211 return True
211 return True
212
212
213 return False
213 return False
214
214
215 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
215 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
216 while True:
216 while True:
217 ok = raw_input(prompt)
217 ok = raw_input(prompt)
218 if ok in ('y', 'ye', 'yes'):
218 if ok in ('y', 'ye', 'yes'):
219 return True
219 return True
220 if ok in ('n', 'no', 'nop', 'nope'):
220 if ok in ('n', 'no', 'nop', 'nope'):
221 return False
221 return False
222 retries = retries - 1
222 retries = retries - 1
223 if retries < 0:
223 if retries < 0:
224 raise IOError
224 raise IOError
225 print complaint
225 print complaint
226
226
227 #propagated from mercurial documentation
227 #propagated from mercurial documentation
228 ui_sections = ['alias', 'auth',
228 ui_sections = ['alias', 'auth',
229 'decode/encode', 'defaults',
229 'decode/encode', 'defaults',
230 'diff', 'email',
230 'diff', 'email',
231 'extensions', 'format',
231 'extensions', 'format',
232 'merge-patterns', 'merge-tools',
232 'merge-patterns', 'merge-tools',
233 'hooks', 'http_proxy',
233 'hooks', 'http_proxy',
234 'smtp', 'patch',
234 'smtp', 'patch',
235 'paths', 'profiling',
235 'paths', 'profiling',
236 'server', 'trusted',
236 'server', 'trusted',
237 'ui', 'web', ]
237 'ui', 'web', ]
238
238
239
239
240 def make_ui(read_from='file', path=None, checkpaths=True):
240 def make_ui(read_from='file', path=None, checkpaths=True):
241 """A function that will read python rc files or database
241 """A function that will read python rc files or database
242 and make an mercurial ui object from read options
242 and make an mercurial ui object from read options
243
243
244 :param path: path to mercurial config file
244 :param path: path to mercurial config file
245 :param checkpaths: check the path
245 :param checkpaths: check the path
246 :param read_from: read from 'file' or 'db'
246 :param read_from: read from 'file' or 'db'
247 """
247 """
248
248
249 baseui = ui.ui()
249 baseui = ui.ui()
250
250
251 #clean the baseui object
251 #clean the baseui object
252 baseui._ocfg = config.config()
252 baseui._ocfg = config.config()
253 baseui._ucfg = config.config()
253 baseui._ucfg = config.config()
254 baseui._tcfg = config.config()
254 baseui._tcfg = config.config()
255
255
256 if read_from == 'file':
256 if read_from == 'file':
257 if not os.path.isfile(path):
257 if not os.path.isfile(path):
258 log.warning('Unable to read config file %s' % path)
258 log.warning('Unable to read config file %s' % path)
259 return False
259 return False
260 log.debug('reading hgrc from %s', path)
260 log.debug('reading hgrc from %s', path)
261 cfg = config.config()
261 cfg = config.config()
262 cfg.read(path)
262 cfg.read(path)
263 for section in ui_sections:
263 for section in ui_sections:
264 for k, v in cfg.items(section):
264 for k, v in cfg.items(section):
265 log.debug('settings ui from file[%s]%s:%s', section, k, v)
265 log.debug('settings ui from file[%s]%s:%s', section, k, v)
266 baseui.setconfig(section, k, v)
266 baseui.setconfig(section, k, v)
267
267
268 elif read_from == 'db':
268 elif read_from == 'db':
269 sa = meta.Session()
269 sa = meta.Session()
270 ret = sa.query(RhodeCodeUi)\
270 ret = sa.query(RhodeCodeUi)\
271 .options(FromCache("sql_cache_short",
271 .options(FromCache("sql_cache_short",
272 "get_hg_ui_settings")).all()
272 "get_hg_ui_settings")).all()
273
273
274 hg_ui = ret
274 hg_ui = ret
275 for ui_ in hg_ui:
275 for ui_ in hg_ui:
276 if ui_.ui_active:
276 if ui_.ui_active:
277 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
277 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
278 ui_.ui_key, ui_.ui_value)
278 ui_.ui_key, ui_.ui_value)
279 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
279 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
280
280
281 meta.Session.remove()
281 meta.Session.remove()
282 return baseui
282 return baseui
283
283
284
284
285 def set_rhodecode_config(config):
285 def set_rhodecode_config(config):
286 """Updates pylons config with new settings from database
286 """Updates pylons config with new settings from database
287
287
288 :param config:
288 :param config:
289 """
289 """
290 hgsettings = RhodeCodeSetting.get_app_settings()
290 hgsettings = RhodeCodeSetting.get_app_settings()
291
291
292 for k, v in hgsettings.items():
292 for k, v in hgsettings.items():
293 config[k] = v
293 config[k] = v
294
294
295
295
296 def invalidate_cache(cache_key, *args):
296 def invalidate_cache(cache_key, *args):
297 """Puts cache invalidation task into db for
297 """Puts cache invalidation task into db for
298 further global cache invalidation
298 further global cache invalidation
299 """
299 """
300
300
301 from rhodecode.model.scm import ScmModel
301 from rhodecode.model.scm import ScmModel
302
302
303 if cache_key.startswith('get_repo_cached_'):
303 if cache_key.startswith('get_repo_cached_'):
304 name = cache_key.split('get_repo_cached_')[-1]
304 name = cache_key.split('get_repo_cached_')[-1]
305 ScmModel().mark_for_invalidation(name)
305 ScmModel().mark_for_invalidation(name)
306
306
307
307
308 class EmptyChangeset(BaseChangeset):
308 class EmptyChangeset(BaseChangeset):
309 """
309 """
310 An dummy empty changeset. It's possible to pass hash when creating
310 An dummy empty changeset. It's possible to pass hash when creating
311 an EmptyChangeset
311 an EmptyChangeset
312 """
312 """
313
313
314 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
314 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
315 self._empty_cs = cs
315 self._empty_cs = cs
316 self.revision = -1
316 self.revision = -1
317 self.message = ''
317 self.message = ''
318 self.author = ''
318 self.author = ''
319 self.date = ''
319 self.date = ''
320 self.repository = repo
320 self.repository = repo
321 self.requested_revision = requested_revision
321 self.requested_revision = requested_revision
322 self.alias = alias
322 self.alias = alias
323
323
324 @LazyProperty
324 @LazyProperty
325 def raw_id(self):
325 def raw_id(self):
326 """Returns raw string identifying this changeset, useful for web
326 """Returns raw string identifying this changeset, useful for web
327 representation.
327 representation.
328 """
328 """
329
329
330 return self._empty_cs
330 return self._empty_cs
331
331
332 @LazyProperty
332 @LazyProperty
333 def branch(self):
333 def branch(self):
334 return get_backend(self.alias).DEFAULT_BRANCH_NAME
334 return get_backend(self.alias).DEFAULT_BRANCH_NAME
335
335
336 @LazyProperty
336 @LazyProperty
337 def short_id(self):
337 def short_id(self):
338 return self.raw_id[:12]
338 return self.raw_id[:12]
339
339
340 def get_file_changeset(self, path):
340 def get_file_changeset(self, path):
341 return self
341 return self
342
342
343 def get_file_content(self, path):
343 def get_file_content(self, path):
344 return u''
344 return u''
345
345
346 def get_file_size(self, path):
346 def get_file_size(self, path):
347 return 0
347 return 0
348
348
349
349
350 def map_groups(groups):
350 def map_groups(groups):
351 """Checks for groups existence, and creates groups structures.
351 """Checks for groups existence, and creates groups structures.
352 It returns last group in structure
352 It returns last group in structure
353
353
354 :param groups: list of groups structure
354 :param groups: list of groups structure
355 """
355 """
356 sa = meta.Session()
356 sa = meta.Session()
357
357
358 parent = None
358 parent = None
359 group = None
359 group = None
360
360
361 # last element is repo in nested groups structure
361 # last element is repo in nested groups structure
362 groups = groups[:-1]
362 groups = groups[:-1]
363
363
364 for lvl, group_name in enumerate(groups):
364 for lvl, group_name in enumerate(groups):
365 group_name = '/'.join(groups[:lvl] + [group_name])
365 group_name = '/'.join(groups[:lvl] + [group_name])
366 group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar()
366 group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar()
367
367
368 if group is None:
368 if group is None:
369 group = RepoGroup(group_name, parent)
369 group = RepoGroup(group_name, parent)
370 sa.add(group)
370 sa.add(group)
371 sa.commit()
371 sa.commit()
372 parent = group
372 parent = group
373 return group
373 return group
374
374
375
375
376 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
376 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
377 """
377 """
378 maps all repos given in initial_repo_list, non existing repositories
378 maps all repos given in initial_repo_list, non existing repositories
379 are created, if remove_obsolete is True it also check for db entries
379 are created, if remove_obsolete is True it also check for db entries
380 that are not in initial_repo_list and removes them.
380 that are not in initial_repo_list and removes them.
381
381
382 :param initial_repo_list: list of repositories found by scanning methods
382 :param initial_repo_list: list of repositories found by scanning methods
383 :param remove_obsolete: check for obsolete entries in database
383 :param remove_obsolete: check for obsolete entries in database
384 """
384 """
385 from rhodecode.model.repo import RepoModel
385 from rhodecode.model.repo import RepoModel
386 sa = meta.Session()
386 sa = meta.Session()
387 rm = RepoModel()
387 rm = RepoModel()
388 user = sa.query(User).filter(User.admin == True).first()
388 user = sa.query(User).filter(User.admin == True).first()
389 if user is None:
389 if user is None:
390 raise Exception('Missing administrative account !')
390 raise Exception('Missing administrative account !')
391 added = []
391 added = []
392 # fixup groups paths to new format on the fly
392
393 # TODO: remove this in future
394 for g in RepoGroup.query().all():
395 g.group_name = g.get_new_name(g.name)
396 sa.add(g)
397 for name, repo in initial_repo_list.items():
393 for name, repo in initial_repo_list.items():
398 group = map_groups(name.split(Repository.url_sep()))
394 group = map_groups(name.split(Repository.url_sep()))
399 if not rm.get_by_repo_name(name, cache=False):
395 if not rm.get_by_repo_name(name, cache=False):
400 log.info('repository %s not found creating default', name)
396 log.info('repository %s not found creating default', name)
401 added.append(name)
397 added.append(name)
402 form_data = {
398 form_data = {
403 'repo_name': name,
399 'repo_name': name,
404 'repo_name_full': name,
400 'repo_name_full': name,
405 'repo_type': repo.alias,
401 'repo_type': repo.alias,
406 'description': repo.description \
402 'description': repo.description \
407 if repo.description != 'unknown' else \
403 if repo.description != 'unknown' else \
408 '%s repository' % name,
404 '%s repository' % name,
409 'private': False,
405 'private': False,
410 'group_id': getattr(group, 'group_id', None)
406 'group_id': getattr(group, 'group_id', None)
411 }
407 }
412 rm.create(form_data, user, just_db=True)
408 rm.create(form_data, user, just_db=True)
413
409
414 removed = []
410 removed = []
415 if remove_obsolete:
411 if remove_obsolete:
416 #remove from database those repositories that are not in the filesystem
412 #remove from database those repositories that are not in the filesystem
417 for repo in sa.query(Repository).all():
413 for repo in sa.query(Repository).all():
418 if repo.repo_name not in initial_repo_list.keys():
414 if repo.repo_name not in initial_repo_list.keys():
419 removed.append(repo.repo_name)
415 removed.append(repo.repo_name)
420 sa.delete(repo)
416 sa.delete(repo)
421 sa.commit()
417 sa.commit()
422
418
423 return added, removed
419 return added, removed
424
420
425 #set cache regions for beaker so celery can utilise it
421 #set cache regions for beaker so celery can utilise it
426 def add_cache(settings):
422 def add_cache(settings):
427 cache_settings = {'regions': None}
423 cache_settings = {'regions': None}
428 for key in settings.keys():
424 for key in settings.keys():
429 for prefix in ['beaker.cache.', 'cache.']:
425 for prefix in ['beaker.cache.', 'cache.']:
430 if key.startswith(prefix):
426 if key.startswith(prefix):
431 name = key.split(prefix)[1].strip()
427 name = key.split(prefix)[1].strip()
432 cache_settings[name] = settings[key].strip()
428 cache_settings[name] = settings[key].strip()
433 if cache_settings['regions']:
429 if cache_settings['regions']:
434 for region in cache_settings['regions'].split(','):
430 for region in cache_settings['regions'].split(','):
435 region = region.strip()
431 region = region.strip()
436 region_settings = {}
432 region_settings = {}
437 for key, value in cache_settings.items():
433 for key, value in cache_settings.items():
438 if key.startswith(region):
434 if key.startswith(region):
439 region_settings[key.split('.')[1]] = value
435 region_settings[key.split('.')[1]] = value
440 region_settings['expire'] = int(region_settings.get('expire',
436 region_settings['expire'] = int(region_settings.get('expire',
441 60))
437 60))
442 region_settings.setdefault('lock_dir',
438 region_settings.setdefault('lock_dir',
443 cache_settings.get('lock_dir'))
439 cache_settings.get('lock_dir'))
444 region_settings.setdefault('data_dir',
440 region_settings.setdefault('data_dir',
445 cache_settings.get('data_dir'))
441 cache_settings.get('data_dir'))
446
442
447 if 'type' not in region_settings:
443 if 'type' not in region_settings:
448 region_settings['type'] = cache_settings.get('type',
444 region_settings['type'] = cache_settings.get('type',
449 'memory')
445 'memory')
450 beaker.cache.cache_regions[region] = region_settings
446 beaker.cache.cache_regions[region] = region_settings
451
447
452
448
453 #==============================================================================
449 #==============================================================================
454 # TEST FUNCTIONS AND CREATORS
450 # TEST FUNCTIONS AND CREATORS
455 #==============================================================================
451 #==============================================================================
456 def create_test_index(repo_location, config, full_index):
452 def create_test_index(repo_location, config, full_index):
457 """
453 """
458 Makes default test index
454 Makes default test index
459
455
460 :param config: test config
456 :param config: test config
461 :param full_index:
457 :param full_index:
462 """
458 """
463
459
464 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
460 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
465 from rhodecode.lib.pidlock import DaemonLock, LockHeld
461 from rhodecode.lib.pidlock import DaemonLock, LockHeld
466
462
467 repo_location = repo_location
463 repo_location = repo_location
468
464
469 index_location = os.path.join(config['app_conf']['index_dir'])
465 index_location = os.path.join(config['app_conf']['index_dir'])
470 if not os.path.exists(index_location):
466 if not os.path.exists(index_location):
471 os.makedirs(index_location)
467 os.makedirs(index_location)
472
468
473 try:
469 try:
474 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
470 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
475 WhooshIndexingDaemon(index_location=index_location,
471 WhooshIndexingDaemon(index_location=index_location,
476 repo_location=repo_location)\
472 repo_location=repo_location)\
477 .run(full_index=full_index)
473 .run(full_index=full_index)
478 l.release()
474 l.release()
479 except LockHeld:
475 except LockHeld:
480 pass
476 pass
481
477
482
478
483 def create_test_env(repos_test_path, config):
479 def create_test_env(repos_test_path, config):
484 """Makes a fresh database and
480 """Makes a fresh database and
485 install test repository into tmp dir
481 install test repository into tmp dir
486 """
482 """
487 from rhodecode.lib.db_manage import DbManage
483 from rhodecode.lib.db_manage import DbManage
488 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
484 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
489 HG_FORK, GIT_FORK, TESTS_TMP_PATH
490 import tarfile
485 import tarfile
491 import shutil
486 import shutil
492 from os.path import abspath
487 from os.path import abspath
493
488
494 # PART ONE create db
489 # PART ONE create db
495 dbconf = config['sqlalchemy.db1.url']
490 dbconf = config['sqlalchemy.db1.url']
496 log.debug('making test db %s', dbconf)
491 log.debug('making test db %s', dbconf)
497
492
498 # create test dir if it doesn't exist
493 # create test dir if it doesn't exist
499 if not os.path.isdir(repos_test_path):
494 if not os.path.isdir(repos_test_path):
500 log.debug('Creating testdir %s' % repos_test_path)
495 log.debug('Creating testdir %s' % repos_test_path)
501 os.makedirs(repos_test_path)
496 os.makedirs(repos_test_path)
502
497
503 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
498 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
504 tests=True)
499 tests=True)
505 dbmanage.create_tables(override=True)
500 dbmanage.create_tables(override=True)
506 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
501 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
507 dbmanage.create_default_user()
502 dbmanage.create_default_user()
508 dbmanage.admin_prompt()
503 dbmanage.admin_prompt()
509 dbmanage.create_permissions()
504 dbmanage.create_permissions()
510 dbmanage.populate_default_permissions()
505 dbmanage.populate_default_permissions()
511
506
512 # PART TWO make test repo
507 # PART TWO make test repo
513 log.debug('making test vcs repositories')
508 log.debug('making test vcs repositories')
514
509
515 idx_path = config['app_conf']['index_dir']
510 idx_path = config['app_conf']['index_dir']
516 data_path = config['app_conf']['cache_dir']
511 data_path = config['app_conf']['cache_dir']
517
512
518 #clean index and data
513 #clean index and data
519 if idx_path and os.path.exists(idx_path):
514 if idx_path and os.path.exists(idx_path):
520 log.debug('remove %s' % idx_path)
515 log.debug('remove %s' % idx_path)
521 shutil.rmtree(idx_path)
516 shutil.rmtree(idx_path)
522
517
523 if data_path and os.path.exists(data_path):
518 if data_path and os.path.exists(data_path):
524 log.debug('remove %s' % data_path)
519 log.debug('remove %s' % data_path)
525 shutil.rmtree(data_path)
520 shutil.rmtree(data_path)
526
521
527 #CREATE DEFAULT HG REPOSITORY
522 #CREATE DEFAULT HG REPOSITORY
528 cur_dir = dn(dn(abspath(__file__)))
523 cur_dir = dn(dn(abspath(__file__)))
529 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
524 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
530 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
525 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
531 tar.close()
526 tar.close()
532
527
533
528
534 #==============================================================================
529 #==============================================================================
535 # PASTER COMMANDS
530 # PASTER COMMANDS
536 #==============================================================================
531 #==============================================================================
537 class BasePasterCommand(Command):
532 class BasePasterCommand(Command):
538 """
533 """
539 Abstract Base Class for paster commands.
534 Abstract Base Class for paster commands.
540
535
541 The celery commands are somewhat aggressive about loading
536 The celery commands are somewhat aggressive about loading
542 celery.conf, and since our module sets the `CELERY_LOADER`
537 celery.conf, and since our module sets the `CELERY_LOADER`
543 environment variable to our loader, we have to bootstrap a bit and
538 environment variable to our loader, we have to bootstrap a bit and
544 make sure we've had a chance to load the pylons config off of the
539 make sure we've had a chance to load the pylons config off of the
545 command line, otherwise everything fails.
540 command line, otherwise everything fails.
546 """
541 """
547 min_args = 1
542 min_args = 1
548 min_args_error = "Please provide a paster config file as an argument."
543 min_args_error = "Please provide a paster config file as an argument."
549 takes_config_file = 1
544 takes_config_file = 1
550 requires_config_file = True
545 requires_config_file = True
551
546
552 def notify_msg(self, msg, log=False):
547 def notify_msg(self, msg, log=False):
553 """Make a notification to user, additionally if logger is passed
548 """Make a notification to user, additionally if logger is passed
554 it logs this action using given logger
549 it logs this action using given logger
555
550
556 :param msg: message that will be printed to user
551 :param msg: message that will be printed to user
557 :param log: logging instance, to use to additionally log this message
552 :param log: logging instance, to use to additionally log this message
558
553
559 """
554 """
560 if log and isinstance(log, logging):
555 if log and isinstance(log, logging):
561 log(msg)
556 log(msg)
562
557
563 def run(self, args):
558 def run(self, args):
564 """
559 """
565 Overrides Command.run
560 Overrides Command.run
566
561
567 Checks for a config file argument and loads it.
562 Checks for a config file argument and loads it.
568 """
563 """
569 if len(args) < self.min_args:
564 if len(args) < self.min_args:
570 raise BadCommand(
565 raise BadCommand(
571 self.min_args_error % {'min_args': self.min_args,
566 self.min_args_error % {'min_args': self.min_args,
572 'actual_args': len(args)})
567 'actual_args': len(args)})
573
568
574 # Decrement because we're going to lob off the first argument.
569 # Decrement because we're going to lob off the first argument.
575 # @@ This is hacky
570 # @@ This is hacky
576 self.min_args -= 1
571 self.min_args -= 1
577 self.bootstrap_config(args[0])
572 self.bootstrap_config(args[0])
578 self.update_parser()
573 self.update_parser()
579 return super(BasePasterCommand, self).run(args[1:])
574 return super(BasePasterCommand, self).run(args[1:])
580
575
581 def update_parser(self):
576 def update_parser(self):
582 """
577 """
583 Abstract method. Allows for the class's parser to be updated
578 Abstract method. Allows for the class's parser to be updated
584 before the superclass's `run` method is called. Necessary to
579 before the superclass's `run` method is called. Necessary to
585 allow options/arguments to be passed through to the underlying
580 allow options/arguments to be passed through to the underlying
586 celery command.
581 celery command.
587 """
582 """
588 raise NotImplementedError("Abstract Method.")
583 raise NotImplementedError("Abstract Method.")
589
584
590 def bootstrap_config(self, conf):
585 def bootstrap_config(self, conf):
591 """
586 """
592 Loads the pylons configuration.
587 Loads the pylons configuration.
593 """
588 """
594 from pylons import config as pylonsconfig
589 from pylons import config as pylonsconfig
595
590
596 path_to_ini_file = os.path.realpath(conf)
591 path_to_ini_file = os.path.realpath(conf)
597 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
592 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
598 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
593 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
599
594
@@ -1,92 +1,92 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.__init__
3 rhodecode.model.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 The application's model objects
6 The application's model objects
7
7
8 :created_on: Nov 25, 2010
8 :created_on: Nov 25, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12
12
13
13
14 :example:
14 :example:
15
15
16 .. code-block:: python
16 .. code-block:: python
17
17
18 from paste.deploy import appconfig
18 from paste.deploy import appconfig
19 from pylons import config
19 from pylons import config
20 from sqlalchemy import engine_from_config
20 from sqlalchemy import engine_from_config
21 from rhodecode.config.environment import load_environment
21 from rhodecode.config.environment import load_environment
22
22
23 conf = appconfig('config:development.ini', relative_to = './../../')
23 conf = appconfig('config:development.ini', relative_to = './../../')
24 load_environment(conf.global_conf, conf.local_conf)
24 load_environment(conf.global_conf, conf.local_conf)
25
25
26 engine = engine_from_config(config, 'sqlalchemy.')
26 engine = engine_from_config(config, 'sqlalchemy.')
27 init_model(engine)
27 init_model(engine)
28 # RUN YOUR CODE HERE
28 # RUN YOUR CODE HERE
29
29
30 """
30 """
31 # This program is free software: you can redistribute it and/or modify
31 # This program is free software: you can redistribute it and/or modify
32 # it under the terms of the GNU General Public License as published by
32 # it under the terms of the GNU General Public License as published by
33 # the Free Software Foundation, either version 3 of the License, or
33 # the Free Software Foundation, either version 3 of the License, or
34 # (at your option) any later version.
34 # (at your option) any later version.
35 #
35 #
36 # This program is distributed in the hope that it will be useful,
36 # This program is distributed in the hope that it will be useful,
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 # GNU General Public License for more details.
39 # GNU General Public License for more details.
40 #
40 #
41 # You should have received a copy of the GNU General Public License
41 # You should have received a copy of the GNU General Public License
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
43
43
44 import logging
44 import logging
45
45
46 from rhodecode.model import meta
46 from rhodecode.model import meta
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def init_model(engine):
51 def init_model(engine):
52 """
52 """
53 Initializes db session, bind the engine with the metadata,
53 Initializes db session, bind the engine with the metadata,
54 Call this before using any of the tables or classes in the model,
54 Call this before using any of the tables or classes in the model,
55 preferably once in application start
55 preferably once in application start
56
56
57 :param engine: engine to bind to
57 :param engine: engine to bind to
58 """
58 """
59 log.info("initializing db for %s", engine)
59 log.info("initializing db for %s", engine)
60 meta.Base.metadata.bind = engine
60 meta.Base.metadata.bind = engine
61
61
62
62
63 class BaseModel(object):
63 class BaseModel(object):
64 """Base Model for all RhodeCode models, it adds sql alchemy session
64 """Base Model for all RhodeCode models, it adds sql alchemy session
65 into instance of model
65 into instance of model
66
66
67 :param sa: If passed it reuses this session instead of creating a new one
67 :param sa: If passed it reuses this session instead of creating a new one
68 """
68 """
69
69
70 def __init__(self, sa=None):
70 def __init__(self, sa=None):
71 if sa is not None:
71 if sa is not None:
72 self.sa = sa
72 self.sa = sa
73 else:
73 else:
74 self.sa = meta.Session()
74 self.sa = meta.Session()
75
75
76 def __get_instance(self, cls, instance):
76 def _get_instance(self, cls, instance):
77 """
77 """
78 Get's instance of given cls using some simple lookup mechanism
78 Get's instance of given cls using some simple lookup mechanism
79
79
80 :param cls: class to fetch
80 :param cls: class to fetch
81 :param instance: int or Instance
81 :param instance: int or Instance
82 """
82 """
83
83
84 if isinstance(instance, cls):
84 if isinstance(instance, cls):
85 return instance
85 return instance
86 elif isinstance(instance, int) or str(instance).isdigit():
86 elif isinstance(instance, int) or str(instance).isdigit():
87 return cls.get(instance)
87 return cls.get(instance)
88 else:
88 else:
89 if instance:
89 if instance:
90 raise Exception('given object must be int or Instance'
90 raise Exception('given object must be int or Instance'
91 ' of %s got %s' % (type(cls),
91 ' of %s got %s' % (type(cls),
92 type(instance)))
92 type(instance)))
@@ -1,130 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib import extract_mentioned_users
32 from rhodecode.lib import extract_mentioned_users
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
36 from rhodecode.model.notification import NotificationModel
36 from rhodecode.model.notification import NotificationModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class ChangesetCommentsModel(BaseModel):
41 class ChangesetCommentsModel(BaseModel):
42
42
43 def __get_changeset_comment(self, changeset_comment):
43 def __get_changeset_comment(self, changeset_comment):
44 return self.__get_instance(ChangesetComment, changeset_comment)
44 return self._get_instance(ChangesetComment, changeset_comment)
45
45
46 def _extract_mentions(self, s):
46 def _extract_mentions(self, s):
47 user_objects = []
47 user_objects = []
48 for username in extract_mentioned_users(s):
48 for username in extract_mentioned_users(s):
49 user_obj = User.get_by_username(username, case_insensitive=True)
49 user_obj = User.get_by_username(username, case_insensitive=True)
50 if user_obj:
50 if user_obj:
51 user_objects.append(user_obj)
51 user_objects.append(user_obj)
52 return user_objects
52 return user_objects
53
53
54 def create(self, text, repo_id, user_id, revision, f_path=None,
54 def create(self, text, repo_id, user_id, revision, f_path=None,
55 line_no=None):
55 line_no=None):
56 """
56 """
57 Creates new comment for changeset
57 Creates new comment for changeset
58
58
59 :param text:
59 :param text:
60 :param repo_id:
60 :param repo_id:
61 :param user_id:
61 :param user_id:
62 :param revision:
62 :param revision:
63 :param f_path:
63 :param f_path:
64 :param line_no:
64 :param line_no:
65 """
65 """
66 if text:
66 if text:
67 repo = Repository.get(repo_id)
67 repo = Repository.get(repo_id)
68 desc = repo.scm_instance.get_changeset(revision).message
68 cs = repo.scm_instance.get_changeset(revision)
69 desc = cs.message
70 author = cs.author_email
69 comment = ChangesetComment()
71 comment = ChangesetComment()
70 comment.repo = repo
72 comment.repo = repo
71 comment.user_id = user_id
73 comment.user_id = user_id
72 comment.revision = revision
74 comment.revision = revision
73 comment.text = text
75 comment.text = text
74 comment.f_path = f_path
76 comment.f_path = f_path
75 comment.line_no = line_no
77 comment.line_no = line_no
76
78
77 self.sa.add(comment)
79 self.sa.add(comment)
78 self.sa.flush()
80 self.sa.flush()
79
81
80 # make notification
82 # make notification
81 line = ''
83 line = ''
82 if line_no:
84 if line_no:
83 line = _('on line %s') % line_no
85 line = _('on line %s') % line_no
84 subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
86 subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
85 {'commit_desc':desc, 'line':line},
87 {'commit_desc':desc, 'line':line},
86 h.url('changeset_home', repo_name=repo.repo_name,
88 h.url('changeset_home', repo_name=repo.repo_name,
87 revision=revision,
89 revision=revision,
88 anchor='comment-%s' % comment.comment_id
90 anchor='comment-%s' % comment.comment_id
89 )
91 )
90 )
92 )
91 body = text
93 body = text
92 recipients = ChangesetComment.get_users(revision=revision)
94 recipients = ChangesetComment.get_users(revision=revision)
93 recipients += self._extract_mentions(body)
95 # add changeset author
96 recipients += [User.get_by_email(author)]
97
94 NotificationModel().create(created_by=user_id, subject=subj,
98 NotificationModel().create(created_by=user_id, subject=subj,
95 body=body, recipients=recipients,
99 body=body, recipients=recipients,
96 type_=Notification.TYPE_CHANGESET_COMMENT)
100 type_=Notification.TYPE_CHANGESET_COMMENT)
97
101
102 mention_recipients = set(self._extract_mentions(body)).difference(recipients)
103 if mention_recipients:
104 subj = _('[Mention]') + ' ' + subj
105 NotificationModel().create(created_by=user_id, subject=subj,
106 body = body, recipients = mention_recipients,
107 type_=Notification.TYPE_CHANGESET_COMMENT)
108
109 self.sa.commit()
98 return comment
110 return comment
99
111
100 def delete(self, comment):
112 def delete(self, comment):
101 """
113 """
102 Deletes given comment
114 Deletes given comment
103
115
104 :param comment_id:
116 :param comment_id:
105 """
117 """
106 comment = self.__get_changeset_comment(comment)
118 comment = self.__get_changeset_comment(comment)
107 self.sa.delete(comment)
119 self.sa.delete(comment)
108
120
109 return comment
121 return comment
110
122
111
123
112 def get_comments(self, repo_id, revision):
124 def get_comments(self, repo_id, revision):
113 return ChangesetComment.query()\
125 return ChangesetComment.query()\
114 .filter(ChangesetComment.repo_id == repo_id)\
126 .filter(ChangesetComment.repo_id == repo_id)\
115 .filter(ChangesetComment.revision == revision)\
127 .filter(ChangesetComment.revision == revision)\
116 .filter(ChangesetComment.line_no == None)\
128 .filter(ChangesetComment.line_no == None)\
117 .filter(ChangesetComment.f_path == None).all()
129 .filter(ChangesetComment.f_path == None).all()
118
130
119 def get_inline_comments(self, repo_id, revision):
131 def get_inline_comments(self, repo_id, revision):
120 comments = self.sa.query(ChangesetComment)\
132 comments = self.sa.query(ChangesetComment)\
121 .filter(ChangesetComment.repo_id == repo_id)\
133 .filter(ChangesetComment.repo_id == repo_id)\
122 .filter(ChangesetComment.revision == revision)\
134 .filter(ChangesetComment.revision == revision)\
123 .filter(ChangesetComment.line_no != None)\
135 .filter(ChangesetComment.line_no != None)\
124 .filter(ChangesetComment.f_path != None).all()
136 .filter(ChangesetComment.f_path != None).all()
125
137
126 paths = defaultdict(lambda:defaultdict(list))
138 paths = defaultdict(lambda:defaultdict(list))
127
139
128 for co in comments:
140 for co in comments:
129 paths[co.f_path][co.line_no].append(co)
141 paths[co.f_path][co.line_no].append(co)
130 return paths.items()
142 return paths.items()
@@ -1,1199 +1,1208 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from datetime import date
30 from datetime import date
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from beaker.cache import cache_region, region_invalidate
36 from beaker.cache import cache_region, region_invalidate
37
37
38 from vcs import get_backend
38 from vcs import get_backend
39 from vcs.utils.helpers import get_scm
39 from vcs.utils.helpers import get_scm
40 from vcs.exceptions import VCSError
40 from vcs.exceptions import VCSError
41 from vcs.utils.lazy import LazyProperty
41 from vcs.utils.lazy import LazyProperty
42
42
43 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
43 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
44 generate_api_key, safe_unicode
44 generate_api_key, safe_unicode
45 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 from rhodecode.lib.exceptions import UsersGroupsAssignedException
46 from rhodecode.lib.compat import json
46 from rhodecode.lib.compat import json
47 from rhodecode.lib.caching_query import FromCache
47 from rhodecode.lib.caching_query import FromCache
48
48
49 from rhodecode.model.meta import Base, Session
49 from rhodecode.model.meta import Base, Session
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53 #==============================================================================
53 #==============================================================================
54 # BASE CLASSES
54 # BASE CLASSES
55 #==============================================================================
55 #==============================================================================
56
56
57 class ModelSerializer(json.JSONEncoder):
57 class ModelSerializer(json.JSONEncoder):
58 """
58 """
59 Simple Serializer for JSON,
59 Simple Serializer for JSON,
60
60
61 usage::
61 usage::
62
62
63 to make object customized for serialization implement a __json__
63 to make object customized for serialization implement a __json__
64 method that will return a dict for serialization into json
64 method that will return a dict for serialization into json
65
65
66 example::
66 example::
67
67
68 class Task(object):
68 class Task(object):
69
69
70 def __init__(self, name, value):
70 def __init__(self, name, value):
71 self.name = name
71 self.name = name
72 self.value = value
72 self.value = value
73
73
74 def __json__(self):
74 def __json__(self):
75 return dict(name=self.name,
75 return dict(name=self.name,
76 value=self.value)
76 value=self.value)
77
77
78 """
78 """
79
79
80 def default(self, obj):
80 def default(self, obj):
81
81
82 if hasattr(obj, '__json__'):
82 if hasattr(obj, '__json__'):
83 return obj.__json__()
83 return obj.__json__()
84 else:
84 else:
85 return json.JSONEncoder.default(self, obj)
85 return json.JSONEncoder.default(self, obj)
86
86
87 class BaseModel(object):
87 class BaseModel(object):
88 """Base Model for all classess
88 """Base Model for all classess
89
89
90 """
90 """
91
91
92 @classmethod
92 @classmethod
93 def _get_keys(cls):
93 def _get_keys(cls):
94 """return column names for this model """
94 """return column names for this model """
95 return class_mapper(cls).c.keys()
95 return class_mapper(cls).c.keys()
96
96
97 def get_dict(self):
97 def get_dict(self):
98 """return dict with keys and values corresponding
98 """return dict with keys and values corresponding
99 to this model data """
99 to this model data """
100
100
101 d = {}
101 d = {}
102 for k in self._get_keys():
102 for k in self._get_keys():
103 d[k] = getattr(self, k)
103 d[k] = getattr(self, k)
104 return d
104 return d
105
105
106 def get_appstruct(self):
106 def get_appstruct(self):
107 """return list with keys and values tupples corresponding
107 """return list with keys and values tupples corresponding
108 to this model data """
108 to this model data """
109
109
110 l = []
110 l = []
111 for k in self._get_keys():
111 for k in self._get_keys():
112 l.append((k, getattr(self, k),))
112 l.append((k, getattr(self, k),))
113 return l
113 return l
114
114
115 def populate_obj(self, populate_dict):
115 def populate_obj(self, populate_dict):
116 """populate model with data from given populate_dict"""
116 """populate model with data from given populate_dict"""
117
117
118 for k in self._get_keys():
118 for k in self._get_keys():
119 if k in populate_dict:
119 if k in populate_dict:
120 setattr(self, k, populate_dict[k])
120 setattr(self, k, populate_dict[k])
121
121
122 @classmethod
122 @classmethod
123 def query(cls):
123 def query(cls):
124 return Session().query(cls)
124 return Session().query(cls)
125
125
126 @classmethod
126 @classmethod
127 def get(cls, id_):
127 def get(cls, id_):
128 if id_:
128 if id_:
129 return cls.query().get(id_)
129 return cls.query().get(id_)
130
130
131 @classmethod
131 @classmethod
132 def getAll(cls):
132 def getAll(cls):
133 return cls.query().all()
133 return cls.query().all()
134
134
135 @classmethod
135 @classmethod
136 def delete(cls, id_):
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
137 obj = cls.query().get(id_)
138 Session().delete(obj)
138 Session().delete(obj)
139
139
140
140
141 class RhodeCodeSetting(Base, BaseModel):
141 class RhodeCodeSetting(Base, BaseModel):
142 __tablename__ = 'rhodecode_settings'
142 __tablename__ = 'rhodecode_settings'
143 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
143 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
144 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
144 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
145 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
145 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147
147
148 def __init__(self, k='', v=''):
148 def __init__(self, k='', v=''):
149 self.app_settings_name = k
149 self.app_settings_name = k
150 self.app_settings_value = v
150 self.app_settings_value = v
151
151
152
152
153 @validates('_app_settings_value')
153 @validates('_app_settings_value')
154 def validate_settings_value(self, key, val):
154 def validate_settings_value(self, key, val):
155 assert type(val) == unicode
155 assert type(val) == unicode
156 return val
156 return val
157
157
158 @hybrid_property
158 @hybrid_property
159 def app_settings_value(self):
159 def app_settings_value(self):
160 v = self._app_settings_value
160 v = self._app_settings_value
161 if v == 'ldap_active':
161 if v == 'ldap_active':
162 v = str2bool(v)
162 v = str2bool(v)
163 return v
163 return v
164
164
165 @app_settings_value.setter
165 @app_settings_value.setter
166 def app_settings_value(self, val):
166 def app_settings_value(self, val):
167 """
167 """
168 Setter that will always make sure we use unicode in app_settings_value
168 Setter that will always make sure we use unicode in app_settings_value
169
169
170 :param val:
170 :param val:
171 """
171 """
172 self._app_settings_value = safe_unicode(val)
172 self._app_settings_value = safe_unicode(val)
173
173
174 def __repr__(self):
174 def __repr__(self):
175 return "<%s('%s:%s')>" % (self.__class__.__name__,
175 return "<%s('%s:%s')>" % (self.__class__.__name__,
176 self.app_settings_name, self.app_settings_value)
176 self.app_settings_name, self.app_settings_value)
177
177
178
178
179 @classmethod
179 @classmethod
180 def get_by_name(cls, ldap_key):
180 def get_by_name(cls, ldap_key):
181 return cls.query()\
181 return cls.query()\
182 .filter(cls.app_settings_name == ldap_key).scalar()
182 .filter(cls.app_settings_name == ldap_key).scalar()
183
183
184 @classmethod
184 @classmethod
185 def get_app_settings(cls, cache=False):
185 def get_app_settings(cls, cache=False):
186
186
187 ret = cls.query()
187 ret = cls.query()
188
188
189 if cache:
189 if cache:
190 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
190 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
191
191
192 if not ret:
192 if not ret:
193 raise Exception('Could not get application settings !')
193 raise Exception('Could not get application settings !')
194 settings = {}
194 settings = {}
195 for each in ret:
195 for each in ret:
196 settings['rhodecode_' + each.app_settings_name] = \
196 settings['rhodecode_' + each.app_settings_name] = \
197 each.app_settings_value
197 each.app_settings_value
198
198
199 return settings
199 return settings
200
200
201 @classmethod
201 @classmethod
202 def get_ldap_settings(cls, cache=False):
202 def get_ldap_settings(cls, cache=False):
203 ret = cls.query()\
203 ret = cls.query()\
204 .filter(cls.app_settings_name.startswith('ldap_')).all()
204 .filter(cls.app_settings_name.startswith('ldap_')).all()
205 fd = {}
205 fd = {}
206 for row in ret:
206 for row in ret:
207 fd.update({row.app_settings_name:row.app_settings_value})
207 fd.update({row.app_settings_name:row.app_settings_value})
208
208
209 return fd
209 return fd
210
210
211
211
212 class RhodeCodeUi(Base, BaseModel):
212 class RhodeCodeUi(Base, BaseModel):
213 __tablename__ = 'rhodecode_ui'
213 __tablename__ = 'rhodecode_ui'
214 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
214 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
215
215
216 HOOK_UPDATE = 'changegroup.update'
216 HOOK_UPDATE = 'changegroup.update'
217 HOOK_REPO_SIZE = 'changegroup.repo_size'
217 HOOK_REPO_SIZE = 'changegroup.repo_size'
218 HOOK_PUSH = 'pretxnchangegroup.push_logger'
218 HOOK_PUSH = 'pretxnchangegroup.push_logger'
219 HOOK_PULL = 'preoutgoing.pull_logger'
219 HOOK_PULL = 'preoutgoing.pull_logger'
220
220
221 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
221 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
222 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
222 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
225 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
226
226
227
227
228 @classmethod
228 @classmethod
229 def get_by_key(cls, key):
229 def get_by_key(cls, key):
230 return cls.query().filter(cls.ui_key == key)
230 return cls.query().filter(cls.ui_key == key)
231
231
232
232
233 @classmethod
233 @classmethod
234 def get_builtin_hooks(cls):
234 def get_builtin_hooks(cls):
235 q = cls.query()
235 q = cls.query()
236 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
236 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
237 cls.HOOK_REPO_SIZE,
237 cls.HOOK_REPO_SIZE,
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
239 return q.all()
239 return q.all()
240
240
241 @classmethod
241 @classmethod
242 def get_custom_hooks(cls):
242 def get_custom_hooks(cls):
243 q = cls.query()
243 q = cls.query()
244 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
244 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
245 cls.HOOK_REPO_SIZE,
245 cls.HOOK_REPO_SIZE,
246 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 q = q.filter(cls.ui_section == 'hooks')
247 q = q.filter(cls.ui_section == 'hooks')
248 return q.all()
248 return q.all()
249
249
250 @classmethod
250 @classmethod
251 def create_or_update_hook(cls, key, val):
251 def create_or_update_hook(cls, key, val):
252 new_ui = cls.get_by_key(key).scalar() or cls()
252 new_ui = cls.get_by_key(key).scalar() or cls()
253 new_ui.ui_section = 'hooks'
253 new_ui.ui_section = 'hooks'
254 new_ui.ui_active = True
254 new_ui.ui_active = True
255 new_ui.ui_key = key
255 new_ui.ui_key = key
256 new_ui.ui_value = val
256 new_ui.ui_value = val
257
257
258 Session().add(new_ui)
258 Session().add(new_ui)
259 Session().commit()
259 Session().commit()
260
260
261
261
262 class User(Base, BaseModel):
262 class User(Base, BaseModel):
263 __tablename__ = 'users'
263 __tablename__ = 'users'
264 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
264 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
265 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
265 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
266 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
268 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
269 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
269 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
270 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
273 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
274 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276
276
277 user_log = relationship('UserLog', cascade='all')
277 user_log = relationship('UserLog', cascade='all')
278 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
278 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
279
279
280 repositories = relationship('Repository')
280 repositories = relationship('Repository')
281 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
281 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
282 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
282 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
283
283
284 group_member = relationship('UsersGroupMember', cascade='all')
284 group_member = relationship('UsersGroupMember', cascade='all')
285
285
286 notifications = relationship('UserNotification')
286 notifications = relationship('UserNotification')
287
287
288 @property
288 @property
289 def full_contact(self):
289 def full_contact(self):
290 return '%s %s <%s>' % (self.name, self.lastname, self.email)
290 return '%s %s <%s>' % (self.name, self.lastname, self.email)
291
291
292 @property
292 @property
293 def short_contact(self):
293 def short_contact(self):
294 return '%s %s' % (self.name, self.lastname)
294 return '%s %s' % (self.name, self.lastname)
295
295
296 @property
296 @property
297 def is_admin(self):
297 def is_admin(self):
298 return self.admin
298 return self.admin
299
299
300 def __repr__(self):
300 def __repr__(self):
301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 self.user_id, self.username)
302 self.user_id, self.username)
303
303
304
304
305 @classmethod
305 @classmethod
306 def get_by_username(cls, username, case_insensitive=False, cache=False):
306 def get_by_username(cls, username, case_insensitive=False, cache=False):
307 if case_insensitive:
307 if case_insensitive:
308 q = cls.query().filter(cls.username.ilike(username))
308 q = cls.query().filter(cls.username.ilike(username))
309 else:
309 else:
310 q = cls.query().filter(cls.username == username)
310 q = cls.query().filter(cls.username == username)
311
311
312 if cache:
312 if cache:
313 q = q.options(FromCache("sql_cache_short",
313 q = q.options(FromCache("sql_cache_short",
314 "get_user_%s" % username))
314 "get_user_%s" % username))
315 return q.scalar()
315 return q.scalar()
316
316
317 @classmethod
317 @classmethod
318 def get_by_api_key(cls, api_key, cache=False):
318 def get_by_api_key(cls, api_key, cache=False):
319 q = cls.query().filter(cls.api_key == api_key)
319 q = cls.query().filter(cls.api_key == api_key)
320
320
321 if cache:
321 if cache:
322 q = q.options(FromCache("sql_cache_short",
322 q = q.options(FromCache("sql_cache_short",
323 "get_api_key_%s" % api_key))
323 "get_api_key_%s" % api_key))
324 return q.scalar()
324 return q.scalar()
325
325
326 @classmethod
327 def get_by_email(cls, email, cache=False):
328 q = cls.query().filter(cls.email == email)
329
330 if cache:
331 q = q.options(FromCache("sql_cache_short",
332 "get_api_key_%s" % email))
333 return q.scalar()
334
326 def update_lastlogin(self):
335 def update_lastlogin(self):
327 """Update user lastlogin"""
336 """Update user lastlogin"""
328
337
329 self.last_login = datetime.datetime.now()
338 self.last_login = datetime.datetime.now()
330 Session().add(self)
339 Session().add(self)
331 Session().commit()
340 Session().commit()
332 log.debug('updated user %s lastlogin', self.username)
341 log.debug('updated user %s lastlogin', self.username)
333
342
334
343
335 class UserLog(Base, BaseModel):
344 class UserLog(Base, BaseModel):
336 __tablename__ = 'user_logs'
345 __tablename__ = 'user_logs'
337 __table_args__ = {'extend_existing':True}
346 __table_args__ = {'extend_existing':True}
338 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
339 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
348 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
340 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
349 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
341 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
350 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
342 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
343 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
344 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
353 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
345
354
346 @property
355 @property
347 def action_as_day(self):
356 def action_as_day(self):
348 return date(*self.action_date.timetuple()[:3])
357 return date(*self.action_date.timetuple()[:3])
349
358
350 user = relationship('User')
359 user = relationship('User')
351 repository = relationship('Repository')
360 repository = relationship('Repository')
352
361
353
362
354 class UsersGroup(Base, BaseModel):
363 class UsersGroup(Base, BaseModel):
355 __tablename__ = 'users_groups'
364 __tablename__ = 'users_groups'
356 __table_args__ = {'extend_existing':True}
365 __table_args__ = {'extend_existing':True}
357
366
358 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
367 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
359 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
368 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
360 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
361
370
362 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
371 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
363
372
364 def __repr__(self):
373 def __repr__(self):
365 return '<userGroup(%s)>' % (self.users_group_name)
374 return '<userGroup(%s)>' % (self.users_group_name)
366
375
367 @classmethod
376 @classmethod
368 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
377 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
369 if case_insensitive:
378 if case_insensitive:
370 gr = cls.query()\
379 gr = cls.query()\
371 .filter(cls.users_group_name.ilike(group_name))
380 .filter(cls.users_group_name.ilike(group_name))
372 else:
381 else:
373 gr = cls.query()\
382 gr = cls.query()\
374 .filter(cls.users_group_name == group_name)
383 .filter(cls.users_group_name == group_name)
375 if cache:
384 if cache:
376 gr = gr.options(FromCache("sql_cache_short",
385 gr = gr.options(FromCache("sql_cache_short",
377 "get_user_%s" % group_name))
386 "get_user_%s" % group_name))
378 return gr.scalar()
387 return gr.scalar()
379
388
380
389
381 @classmethod
390 @classmethod
382 def get(cls, users_group_id, cache=False):
391 def get(cls, users_group_id, cache=False):
383 users_group = cls.query()
392 users_group = cls.query()
384 if cache:
393 if cache:
385 users_group = users_group.options(FromCache("sql_cache_short",
394 users_group = users_group.options(FromCache("sql_cache_short",
386 "get_users_group_%s" % users_group_id))
395 "get_users_group_%s" % users_group_id))
387 return users_group.get(users_group_id)
396 return users_group.get(users_group_id)
388
397
389 @classmethod
398 @classmethod
390 def create(cls, form_data):
399 def create(cls, form_data):
391 try:
400 try:
392 new_users_group = cls()
401 new_users_group = cls()
393 for k, v in form_data.items():
402 for k, v in form_data.items():
394 setattr(new_users_group, k, v)
403 setattr(new_users_group, k, v)
395
404
396 Session().add(new_users_group)
405 Session().add(new_users_group)
397 Session().commit()
406 Session().commit()
398 return new_users_group
407 return new_users_group
399 except:
408 except:
400 log.error(traceback.format_exc())
409 log.error(traceback.format_exc())
401 Session().rollback()
410 Session().rollback()
402 raise
411 raise
403
412
404 @classmethod
413 @classmethod
405 def update(cls, users_group_id, form_data):
414 def update(cls, users_group_id, form_data):
406
415
407 try:
416 try:
408 users_group = cls.get(users_group_id, cache=False)
417 users_group = cls.get(users_group_id, cache=False)
409
418
410 for k, v in form_data.items():
419 for k, v in form_data.items():
411 if k == 'users_group_members':
420 if k == 'users_group_members':
412 users_group.members = []
421 users_group.members = []
413 Session().flush()
422 Session().flush()
414 members_list = []
423 members_list = []
415 if v:
424 if v:
416 v = [v] if isinstance(v, basestring) else v
425 v = [v] if isinstance(v, basestring) else v
417 for u_id in set(v):
426 for u_id in set(v):
418 member = UsersGroupMember(users_group_id, u_id)
427 member = UsersGroupMember(users_group_id, u_id)
419 members_list.append(member)
428 members_list.append(member)
420 setattr(users_group, 'members', members_list)
429 setattr(users_group, 'members', members_list)
421 setattr(users_group, k, v)
430 setattr(users_group, k, v)
422
431
423 Session().add(users_group)
432 Session().add(users_group)
424 Session().commit()
433 Session().commit()
425 except:
434 except:
426 log.error(traceback.format_exc())
435 log.error(traceback.format_exc())
427 Session().rollback()
436 Session().rollback()
428 raise
437 raise
429
438
430 @classmethod
439 @classmethod
431 def delete(cls, users_group_id):
440 def delete(cls, users_group_id):
432 try:
441 try:
433
442
434 # check if this group is not assigned to repo
443 # check if this group is not assigned to repo
435 assigned_groups = UsersGroupRepoToPerm.query()\
444 assigned_groups = UsersGroupRepoToPerm.query()\
436 .filter(UsersGroupRepoToPerm.users_group_id ==
445 .filter(UsersGroupRepoToPerm.users_group_id ==
437 users_group_id).all()
446 users_group_id).all()
438
447
439 if assigned_groups:
448 if assigned_groups:
440 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
449 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
441 assigned_groups)
450 assigned_groups)
442
451
443 users_group = cls.get(users_group_id, cache=False)
452 users_group = cls.get(users_group_id, cache=False)
444 Session().delete(users_group)
453 Session().delete(users_group)
445 Session().commit()
454 Session().commit()
446 except:
455 except:
447 log.error(traceback.format_exc())
456 log.error(traceback.format_exc())
448 Session().rollback()
457 Session().rollback()
449 raise
458 raise
450
459
451 class UsersGroupMember(Base, BaseModel):
460 class UsersGroupMember(Base, BaseModel):
452 __tablename__ = 'users_groups_members'
461 __tablename__ = 'users_groups_members'
453 __table_args__ = {'extend_existing':True}
462 __table_args__ = {'extend_existing':True}
454
463
455 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
464 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
456 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
465 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
458
467
459 user = relationship('User', lazy='joined')
468 user = relationship('User', lazy='joined')
460 users_group = relationship('UsersGroup')
469 users_group = relationship('UsersGroup')
461
470
462 def __init__(self, gr_id='', u_id=''):
471 def __init__(self, gr_id='', u_id=''):
463 self.users_group_id = gr_id
472 self.users_group_id = gr_id
464 self.user_id = u_id
473 self.user_id = u_id
465
474
466 @staticmethod
475 @staticmethod
467 def add_user_to_group(group, user):
476 def add_user_to_group(group, user):
468 ugm = UsersGroupMember()
477 ugm = UsersGroupMember()
469 ugm.users_group = group
478 ugm.users_group = group
470 ugm.user = user
479 ugm.user = user
471 Session().add(ugm)
480 Session().add(ugm)
472 Session().commit()
481 Session().commit()
473 return ugm
482 return ugm
474
483
475 class Repository(Base, BaseModel):
484 class Repository(Base, BaseModel):
476 __tablename__ = 'repositories'
485 __tablename__ = 'repositories'
477 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
486 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
478
487
479 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
480 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
489 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
481 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
490 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
482 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
491 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
483 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
484 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
493 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
485 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
494 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
486 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
495 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
487 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
496 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
488 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
497 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
489
498
490 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
499 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
491 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
492
501
493
502
494 user = relationship('User')
503 user = relationship('User')
495 fork = relationship('Repository', remote_side=repo_id)
504 fork = relationship('Repository', remote_side=repo_id)
496 group = relationship('RepoGroup')
505 group = relationship('RepoGroup')
497 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
506 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
498 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
507 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
499 stats = relationship('Statistics', cascade='all', uselist=False)
508 stats = relationship('Statistics', cascade='all', uselist=False)
500
509
501 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
510 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
502
511
503 logs = relationship('UserLog', cascade='all')
512 logs = relationship('UserLog', cascade='all')
504
513
505 def __repr__(self):
514 def __repr__(self):
506 return "<%s('%s:%s')>" % (self.__class__.__name__,
515 return "<%s('%s:%s')>" % (self.__class__.__name__,
507 self.repo_id, self.repo_name)
516 self.repo_id, self.repo_name)
508
517
509 @classmethod
518 @classmethod
510 def url_sep(cls):
519 def url_sep(cls):
511 return '/'
520 return '/'
512
521
513 @classmethod
522 @classmethod
514 def get_by_repo_name(cls, repo_name):
523 def get_by_repo_name(cls, repo_name):
515 q = Session().query(cls).filter(cls.repo_name == repo_name)
524 q = Session().query(cls).filter(cls.repo_name == repo_name)
516 q = q.options(joinedload(Repository.fork))\
525 q = q.options(joinedload(Repository.fork))\
517 .options(joinedload(Repository.user))\
526 .options(joinedload(Repository.user))\
518 .options(joinedload(Repository.group))
527 .options(joinedload(Repository.group))
519 return q.one()
528 return q.one()
520
529
521 @classmethod
530 @classmethod
522 def get_repo_forks(cls, repo_id):
531 def get_repo_forks(cls, repo_id):
523 return cls.query().filter(Repository.fork_id == repo_id)
532 return cls.query().filter(Repository.fork_id == repo_id)
524
533
525 @classmethod
534 @classmethod
526 def base_path(cls):
535 def base_path(cls):
527 """
536 """
528 Returns base path when all repos are stored
537 Returns base path when all repos are stored
529
538
530 :param cls:
539 :param cls:
531 """
540 """
532 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
541 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
533 cls.url_sep())
542 cls.url_sep())
534 q.options(FromCache("sql_cache_short", "repository_repo_path"))
543 q.options(FromCache("sql_cache_short", "repository_repo_path"))
535 return q.one().ui_value
544 return q.one().ui_value
536
545
537 @property
546 @property
538 def just_name(self):
547 def just_name(self):
539 return self.repo_name.split(Repository.url_sep())[-1]
548 return self.repo_name.split(Repository.url_sep())[-1]
540
549
541 @property
550 @property
542 def groups_with_parents(self):
551 def groups_with_parents(self):
543 groups = []
552 groups = []
544 if self.group is None:
553 if self.group is None:
545 return groups
554 return groups
546
555
547 cur_gr = self.group
556 cur_gr = self.group
548 groups.insert(0, cur_gr)
557 groups.insert(0, cur_gr)
549 while 1:
558 while 1:
550 gr = getattr(cur_gr, 'parent_group', None)
559 gr = getattr(cur_gr, 'parent_group', None)
551 cur_gr = cur_gr.parent_group
560 cur_gr = cur_gr.parent_group
552 if gr is None:
561 if gr is None:
553 break
562 break
554 groups.insert(0, gr)
563 groups.insert(0, gr)
555
564
556 return groups
565 return groups
557
566
558 @property
567 @property
559 def groups_and_repo(self):
568 def groups_and_repo(self):
560 return self.groups_with_parents, self.just_name
569 return self.groups_with_parents, self.just_name
561
570
562 @LazyProperty
571 @LazyProperty
563 def repo_path(self):
572 def repo_path(self):
564 """
573 """
565 Returns base full path for that repository means where it actually
574 Returns base full path for that repository means where it actually
566 exists on a filesystem
575 exists on a filesystem
567 """
576 """
568 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
577 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
569 Repository.url_sep())
578 Repository.url_sep())
570 q.options(FromCache("sql_cache_short", "repository_repo_path"))
579 q.options(FromCache("sql_cache_short", "repository_repo_path"))
571 return q.one().ui_value
580 return q.one().ui_value
572
581
573 @property
582 @property
574 def repo_full_path(self):
583 def repo_full_path(self):
575 p = [self.repo_path]
584 p = [self.repo_path]
576 # we need to split the name by / since this is how we store the
585 # we need to split the name by / since this is how we store the
577 # names in the database, but that eventually needs to be converted
586 # names in the database, but that eventually needs to be converted
578 # into a valid system path
587 # into a valid system path
579 p += self.repo_name.split(Repository.url_sep())
588 p += self.repo_name.split(Repository.url_sep())
580 return os.path.join(*p)
589 return os.path.join(*p)
581
590
582 def get_new_name(self, repo_name):
591 def get_new_name(self, repo_name):
583 """
592 """
584 returns new full repository name based on assigned group and new new
593 returns new full repository name based on assigned group and new new
585
594
586 :param group_name:
595 :param group_name:
587 """
596 """
588 path_prefix = self.group.full_path_splitted if self.group else []
597 path_prefix = self.group.full_path_splitted if self.group else []
589 return Repository.url_sep().join(path_prefix + [repo_name])
598 return Repository.url_sep().join(path_prefix + [repo_name])
590
599
591 @property
600 @property
592 def _ui(self):
601 def _ui(self):
593 """
602 """
594 Creates an db based ui object for this repository
603 Creates an db based ui object for this repository
595 """
604 """
596 from mercurial import ui
605 from mercurial import ui
597 from mercurial import config
606 from mercurial import config
598 baseui = ui.ui()
607 baseui = ui.ui()
599
608
600 #clean the baseui object
609 #clean the baseui object
601 baseui._ocfg = config.config()
610 baseui._ocfg = config.config()
602 baseui._ucfg = config.config()
611 baseui._ucfg = config.config()
603 baseui._tcfg = config.config()
612 baseui._tcfg = config.config()
604
613
605
614
606 ret = RhodeCodeUi.query()\
615 ret = RhodeCodeUi.query()\
607 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
616 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
608
617
609 hg_ui = ret
618 hg_ui = ret
610 for ui_ in hg_ui:
619 for ui_ in hg_ui:
611 if ui_.ui_active:
620 if ui_.ui_active:
612 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
621 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
613 ui_.ui_key, ui_.ui_value)
622 ui_.ui_key, ui_.ui_value)
614 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
623 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
615
624
616 return baseui
625 return baseui
617
626
618 @classmethod
627 @classmethod
619 def is_valid(cls, repo_name):
628 def is_valid(cls, repo_name):
620 """
629 """
621 returns True if given repo name is a valid filesystem repository
630 returns True if given repo name is a valid filesystem repository
622
631
623 @param cls:
632 @param cls:
624 @param repo_name:
633 @param repo_name:
625 """
634 """
626 from rhodecode.lib.utils import is_valid_repo
635 from rhodecode.lib.utils import is_valid_repo
627
636
628 return is_valid_repo(repo_name, cls.base_path())
637 return is_valid_repo(repo_name, cls.base_path())
629
638
630
639
631 #==========================================================================
640 #==========================================================================
632 # SCM PROPERTIES
641 # SCM PROPERTIES
633 #==========================================================================
642 #==========================================================================
634
643
635 def get_changeset(self, rev):
644 def get_changeset(self, rev):
636 return get_changeset_safe(self.scm_instance, rev)
645 return get_changeset_safe(self.scm_instance, rev)
637
646
638 @property
647 @property
639 def tip(self):
648 def tip(self):
640 return self.get_changeset('tip')
649 return self.get_changeset('tip')
641
650
642 @property
651 @property
643 def author(self):
652 def author(self):
644 return self.tip.author
653 return self.tip.author
645
654
646 @property
655 @property
647 def last_change(self):
656 def last_change(self):
648 return self.scm_instance.last_change
657 return self.scm_instance.last_change
649
658
650 #==========================================================================
659 #==========================================================================
651 # SCM CACHE INSTANCE
660 # SCM CACHE INSTANCE
652 #==========================================================================
661 #==========================================================================
653
662
654 @property
663 @property
655 def invalidate(self):
664 def invalidate(self):
656 return CacheInvalidation.invalidate(self.repo_name)
665 return CacheInvalidation.invalidate(self.repo_name)
657
666
658 def set_invalidate(self):
667 def set_invalidate(self):
659 """
668 """
660 set a cache for invalidation for this instance
669 set a cache for invalidation for this instance
661 """
670 """
662 CacheInvalidation.set_invalidate(self.repo_name)
671 CacheInvalidation.set_invalidate(self.repo_name)
663
672
664 @LazyProperty
673 @LazyProperty
665 def scm_instance(self):
674 def scm_instance(self):
666 return self.__get_instance()
675 return self.__get_instance()
667
676
668 @property
677 @property
669 def scm_instance_cached(self):
678 def scm_instance_cached(self):
670 @cache_region('long_term')
679 @cache_region('long_term')
671 def _c(repo_name):
680 def _c(repo_name):
672 return self.__get_instance()
681 return self.__get_instance()
673 rn = self.repo_name
682 rn = self.repo_name
674
683
675 inv = self.invalidate
684 inv = self.invalidate
676 if inv is not None:
685 if inv is not None:
677 region_invalidate(_c, None, rn)
686 region_invalidate(_c, None, rn)
678 # update our cache
687 # update our cache
679 CacheInvalidation.set_valid(inv.cache_key)
688 CacheInvalidation.set_valid(inv.cache_key)
680 return _c(rn)
689 return _c(rn)
681
690
682 def __get_instance(self):
691 def __get_instance(self):
683
692
684 repo_full_path = self.repo_full_path
693 repo_full_path = self.repo_full_path
685
694
686 try:
695 try:
687 alias = get_scm(repo_full_path)[0]
696 alias = get_scm(repo_full_path)[0]
688 log.debug('Creating instance of %s repository', alias)
697 log.debug('Creating instance of %s repository', alias)
689 backend = get_backend(alias)
698 backend = get_backend(alias)
690 except VCSError:
699 except VCSError:
691 log.error(traceback.format_exc())
700 log.error(traceback.format_exc())
692 log.error('Perhaps this repository is in db and not in '
701 log.error('Perhaps this repository is in db and not in '
693 'filesystem run rescan repositories with '
702 'filesystem run rescan repositories with '
694 '"destroy old data " option from admin panel')
703 '"destroy old data " option from admin panel')
695 return
704 return
696
705
697 if alias == 'hg':
706 if alias == 'hg':
698
707
699 repo = backend(safe_str(repo_full_path), create=False,
708 repo = backend(safe_str(repo_full_path), create=False,
700 baseui=self._ui)
709 baseui=self._ui)
701 # skip hidden web repository
710 # skip hidden web repository
702 if repo._get_hidden():
711 if repo._get_hidden():
703 return
712 return
704 else:
713 else:
705 repo = backend(repo_full_path, create=False)
714 repo = backend(repo_full_path, create=False)
706
715
707 return repo
716 return repo
708
717
709
718
710 class RepoGroup(Base, BaseModel):
719 class RepoGroup(Base, BaseModel):
711 __tablename__ = 'groups'
720 __tablename__ = 'groups'
712 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
721 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
713 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
722 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
714 __mapper_args__ = {'order_by':'group_name'}
723 __mapper_args__ = {'order_by':'group_name'}
715
724
716 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
725 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
717 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
726 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
718 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
727 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
719 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
728 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
720
729
721 parent_group = relationship('RepoGroup', remote_side=group_id)
730 parent_group = relationship('RepoGroup', remote_side=group_id)
722
731
723
732
724 def __init__(self, group_name='', parent_group=None):
733 def __init__(self, group_name='', parent_group=None):
725 self.group_name = group_name
734 self.group_name = group_name
726 self.parent_group = parent_group
735 self.parent_group = parent_group
727
736
728 def __repr__(self):
737 def __repr__(self):
729 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
738 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
730 self.group_name)
739 self.group_name)
731
740
732 @classmethod
741 @classmethod
733 def groups_choices(cls):
742 def groups_choices(cls):
734 from webhelpers.html import literal as _literal
743 from webhelpers.html import literal as _literal
735 repo_groups = [('', '')]
744 repo_groups = [('', '')]
736 sep = ' &raquo; '
745 sep = ' &raquo; '
737 _name = lambda k: _literal(sep.join(k))
746 _name = lambda k: _literal(sep.join(k))
738
747
739 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
748 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
740 for x in cls.query().all()])
749 for x in cls.query().all()])
741
750
742 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
751 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
743 return repo_groups
752 return repo_groups
744
753
745 @classmethod
754 @classmethod
746 def url_sep(cls):
755 def url_sep(cls):
747 return '/'
756 return '/'
748
757
749 @classmethod
758 @classmethod
750 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
759 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
751 if case_insensitive:
760 if case_insensitive:
752 gr = cls.query()\
761 gr = cls.query()\
753 .filter(cls.group_name.ilike(group_name))
762 .filter(cls.group_name.ilike(group_name))
754 else:
763 else:
755 gr = cls.query()\
764 gr = cls.query()\
756 .filter(cls.group_name == group_name)
765 .filter(cls.group_name == group_name)
757 if cache:
766 if cache:
758 gr = gr.options(FromCache("sql_cache_short",
767 gr = gr.options(FromCache("sql_cache_short",
759 "get_group_%s" % group_name))
768 "get_group_%s" % group_name))
760 return gr.scalar()
769 return gr.scalar()
761
770
762 @property
771 @property
763 def parents(self):
772 def parents(self):
764 parents_recursion_limit = 5
773 parents_recursion_limit = 5
765 groups = []
774 groups = []
766 if self.parent_group is None:
775 if self.parent_group is None:
767 return groups
776 return groups
768 cur_gr = self.parent_group
777 cur_gr = self.parent_group
769 groups.insert(0, cur_gr)
778 groups.insert(0, cur_gr)
770 cnt = 0
779 cnt = 0
771 while 1:
780 while 1:
772 cnt += 1
781 cnt += 1
773 gr = getattr(cur_gr, 'parent_group', None)
782 gr = getattr(cur_gr, 'parent_group', None)
774 cur_gr = cur_gr.parent_group
783 cur_gr = cur_gr.parent_group
775 if gr is None:
784 if gr is None:
776 break
785 break
777 if cnt == parents_recursion_limit:
786 if cnt == parents_recursion_limit:
778 # this will prevent accidental infinit loops
787 # this will prevent accidental infinit loops
779 log.error('group nested more than %s' %
788 log.error('group nested more than %s' %
780 parents_recursion_limit)
789 parents_recursion_limit)
781 break
790 break
782
791
783 groups.insert(0, gr)
792 groups.insert(0, gr)
784 return groups
793 return groups
785
794
786 @property
795 @property
787 def children(self):
796 def children(self):
788 return RepoGroup.query().filter(RepoGroup.parent_group == self)
797 return RepoGroup.query().filter(RepoGroup.parent_group == self)
789
798
790 @property
799 @property
791 def name(self):
800 def name(self):
792 return self.group_name.split(RepoGroup.url_sep())[-1]
801 return self.group_name.split(RepoGroup.url_sep())[-1]
793
802
794 @property
803 @property
795 def full_path(self):
804 def full_path(self):
796 return self.group_name
805 return self.group_name
797
806
798 @property
807 @property
799 def full_path_splitted(self):
808 def full_path_splitted(self):
800 return self.group_name.split(RepoGroup.url_sep())
809 return self.group_name.split(RepoGroup.url_sep())
801
810
802 @property
811 @property
803 def repositories(self):
812 def repositories(self):
804 return Repository.query().filter(Repository.group == self)
813 return Repository.query().filter(Repository.group == self)
805
814
806 @property
815 @property
807 def repositories_recursive_count(self):
816 def repositories_recursive_count(self):
808 cnt = self.repositories.count()
817 cnt = self.repositories.count()
809
818
810 def children_count(group):
819 def children_count(group):
811 cnt = 0
820 cnt = 0
812 for child in group.children:
821 for child in group.children:
813 cnt += child.repositories.count()
822 cnt += child.repositories.count()
814 cnt += children_count(child)
823 cnt += children_count(child)
815 return cnt
824 return cnt
816
825
817 return cnt + children_count(self)
826 return cnt + children_count(self)
818
827
819
828
820 def get_new_name(self, group_name):
829 def get_new_name(self, group_name):
821 """
830 """
822 returns new full group name based on parent and new name
831 returns new full group name based on parent and new name
823
832
824 :param group_name:
833 :param group_name:
825 """
834 """
826 path_prefix = (self.parent_group.full_path_splitted if
835 path_prefix = (self.parent_group.full_path_splitted if
827 self.parent_group else [])
836 self.parent_group else [])
828 return RepoGroup.url_sep().join(path_prefix + [group_name])
837 return RepoGroup.url_sep().join(path_prefix + [group_name])
829
838
830
839
831 class Permission(Base, BaseModel):
840 class Permission(Base, BaseModel):
832 __tablename__ = 'permissions'
841 __tablename__ = 'permissions'
833 __table_args__ = {'extend_existing':True}
842 __table_args__ = {'extend_existing':True}
834 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
843 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
835 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
844 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
836 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
845 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
837
846
838 def __repr__(self):
847 def __repr__(self):
839 return "<%s('%s:%s')>" % (self.__class__.__name__,
848 return "<%s('%s:%s')>" % (self.__class__.__name__,
840 self.permission_id, self.permission_name)
849 self.permission_id, self.permission_name)
841
850
842 @classmethod
851 @classmethod
843 def get_by_key(cls, key):
852 def get_by_key(cls, key):
844 return cls.query().filter(cls.permission_name == key).scalar()
853 return cls.query().filter(cls.permission_name == key).scalar()
845
854
846 class UserRepoToPerm(Base, BaseModel):
855 class UserRepoToPerm(Base, BaseModel):
847 __tablename__ = 'repo_to_perm'
856 __tablename__ = 'repo_to_perm'
848 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
857 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
849 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
858 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
851 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
860 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
852 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
861 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
853
862
854 user = relationship('User')
863 user = relationship('User')
855 permission = relationship('Permission')
864 permission = relationship('Permission')
856 repository = relationship('Repository')
865 repository = relationship('Repository')
857
866
858 class UserToPerm(Base, BaseModel):
867 class UserToPerm(Base, BaseModel):
859 __tablename__ = 'user_to_perm'
868 __tablename__ = 'user_to_perm'
860 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
869 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
861 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
870 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
862 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
871 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
863 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
872 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
864
873
865 user = relationship('User')
874 user = relationship('User')
866 permission = relationship('Permission')
875 permission = relationship('Permission')
867
876
868 @classmethod
877 @classmethod
869 def has_perm(cls, user_id, perm):
878 def has_perm(cls, user_id, perm):
870 if not isinstance(perm, Permission):
879 if not isinstance(perm, Permission):
871 raise Exception('perm needs to be an instance of Permission class')
880 raise Exception('perm needs to be an instance of Permission class')
872
881
873 return cls.query().filter(cls.user_id == user_id)\
882 return cls.query().filter(cls.user_id == user_id)\
874 .filter(cls.permission == perm).scalar() is not None
883 .filter(cls.permission == perm).scalar() is not None
875
884
876 @classmethod
885 @classmethod
877 def grant_perm(cls, user_id, perm):
886 def grant_perm(cls, user_id, perm):
878 if not isinstance(perm, Permission):
887 if not isinstance(perm, Permission):
879 raise Exception('perm needs to be an instance of Permission class')
888 raise Exception('perm needs to be an instance of Permission class')
880
889
881 new = cls()
890 new = cls()
882 new.user_id = user_id
891 new.user_id = user_id
883 new.permission = perm
892 new.permission = perm
884 try:
893 try:
885 Session().add(new)
894 Session().add(new)
886 Session().commit()
895 Session().commit()
887 except:
896 except:
888 Session().rollback()
897 Session().rollback()
889
898
890
899
891 @classmethod
900 @classmethod
892 def revoke_perm(cls, user_id, perm):
901 def revoke_perm(cls, user_id, perm):
893 if not isinstance(perm, Permission):
902 if not isinstance(perm, Permission):
894 raise Exception('perm needs to be an instance of Permission class')
903 raise Exception('perm needs to be an instance of Permission class')
895
904
896 try:
905 try:
897 obj = cls.query().filter(cls.user_id == user_id)\
906 obj = cls.query().filter(cls.user_id == user_id)\
898 .filter(cls.permission == perm).one()
907 .filter(cls.permission == perm).one()
899 Session().delete(obj)
908 Session().delete(obj)
900 Session().commit()
909 Session().commit()
901 except:
910 except:
902 Session().rollback()
911 Session().rollback()
903
912
904 class UsersGroupRepoToPerm(Base, BaseModel):
913 class UsersGroupRepoToPerm(Base, BaseModel):
905 __tablename__ = 'users_group_repo_to_perm'
914 __tablename__ = 'users_group_repo_to_perm'
906 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
915 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
907 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
916 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
908 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
917 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
909 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
910 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
919 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
911
920
912 users_group = relationship('UsersGroup')
921 users_group = relationship('UsersGroup')
913 permission = relationship('Permission')
922 permission = relationship('Permission')
914 repository = relationship('Repository')
923 repository = relationship('Repository')
915
924
916 def __repr__(self):
925 def __repr__(self):
917 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
926 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
918
927
919 class UsersGroupToPerm(Base, BaseModel):
928 class UsersGroupToPerm(Base, BaseModel):
920 __tablename__ = 'users_group_to_perm'
929 __tablename__ = 'users_group_to_perm'
921 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
930 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
931 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
932 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
924
933
925 users_group = relationship('UsersGroup')
934 users_group = relationship('UsersGroup')
926 permission = relationship('Permission')
935 permission = relationship('Permission')
927
936
928
937
929 @classmethod
938 @classmethod
930 def has_perm(cls, users_group_id, perm):
939 def has_perm(cls, users_group_id, perm):
931 if not isinstance(perm, Permission):
940 if not isinstance(perm, Permission):
932 raise Exception('perm needs to be an instance of Permission class')
941 raise Exception('perm needs to be an instance of Permission class')
933
942
934 return cls.query().filter(cls.users_group_id ==
943 return cls.query().filter(cls.users_group_id ==
935 users_group_id)\
944 users_group_id)\
936 .filter(cls.permission == perm)\
945 .filter(cls.permission == perm)\
937 .scalar() is not None
946 .scalar() is not None
938
947
939 @classmethod
948 @classmethod
940 def grant_perm(cls, users_group_id, perm):
949 def grant_perm(cls, users_group_id, perm):
941 if not isinstance(perm, Permission):
950 if not isinstance(perm, Permission):
942 raise Exception('perm needs to be an instance of Permission class')
951 raise Exception('perm needs to be an instance of Permission class')
943
952
944 new = cls()
953 new = cls()
945 new.users_group_id = users_group_id
954 new.users_group_id = users_group_id
946 new.permission = perm
955 new.permission = perm
947 try:
956 try:
948 Session().add(new)
957 Session().add(new)
949 Session().commit()
958 Session().commit()
950 except:
959 except:
951 Session().rollback()
960 Session().rollback()
952
961
953
962
954 @classmethod
963 @classmethod
955 def revoke_perm(cls, users_group_id, perm):
964 def revoke_perm(cls, users_group_id, perm):
956 if not isinstance(perm, Permission):
965 if not isinstance(perm, Permission):
957 raise Exception('perm needs to be an instance of Permission class')
966 raise Exception('perm needs to be an instance of Permission class')
958
967
959 try:
968 try:
960 obj = cls.query().filter(cls.users_group_id == users_group_id)\
969 obj = cls.query().filter(cls.users_group_id == users_group_id)\
961 .filter(cls.permission == perm).one()
970 .filter(cls.permission == perm).one()
962 Session().delete(obj)
971 Session().delete(obj)
963 Session().commit()
972 Session().commit()
964 except:
973 except:
965 Session().rollback()
974 Session().rollback()
966
975
967
976
968 class UserRepoGroupToPerm(Base, BaseModel):
977 class UserRepoGroupToPerm(Base, BaseModel):
969 __tablename__ = 'group_to_perm'
978 __tablename__ = 'group_to_perm'
970 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
979 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
971
980
972 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
981 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
973 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
974 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
983 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
975 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
984 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
976
985
977 user = relationship('User')
986 user = relationship('User')
978 permission = relationship('Permission')
987 permission = relationship('Permission')
979 group = relationship('RepoGroup')
988 group = relationship('RepoGroup')
980
989
981 class UsersGroupRepoGroupToPerm(Base, BaseModel):
990 class UsersGroupRepoGroupToPerm(Base, BaseModel):
982 __tablename__ = 'users_group_repo_group_to_perm'
991 __tablename__ = 'users_group_repo_group_to_perm'
983 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
992 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
984
993
985 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)
994 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)
986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
995 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
996 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
997 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
989
998
990 users_group = relationship('UsersGroup')
999 users_group = relationship('UsersGroup')
991 permission = relationship('Permission')
1000 permission = relationship('Permission')
992 group = relationship('RepoGroup')
1001 group = relationship('RepoGroup')
993
1002
994 class Statistics(Base, BaseModel):
1003 class Statistics(Base, BaseModel):
995 __tablename__ = 'statistics'
1004 __tablename__ = 'statistics'
996 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1005 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
997 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1006 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
998 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1007 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
999 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1008 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1000 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1009 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1001 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1010 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1002 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1011 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1003
1012
1004 repository = relationship('Repository', single_parent=True)
1013 repository = relationship('Repository', single_parent=True)
1005
1014
1006 class UserFollowing(Base, BaseModel):
1015 class UserFollowing(Base, BaseModel):
1007 __tablename__ = 'user_followings'
1016 __tablename__ = 'user_followings'
1008 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1017 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1009 UniqueConstraint('user_id', 'follows_user_id')
1018 UniqueConstraint('user_id', 'follows_user_id')
1010 , {'extend_existing':True})
1019 , {'extend_existing':True})
1011
1020
1012 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1021 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1013 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1022 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1014 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1023 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1015 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1024 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1016 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1025 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1017
1026
1018 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1027 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1019
1028
1020 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1029 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1021 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1030 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1022
1031
1023
1032
1024 @classmethod
1033 @classmethod
1025 def get_repo_followers(cls, repo_id):
1034 def get_repo_followers(cls, repo_id):
1026 return cls.query().filter(cls.follows_repo_id == repo_id)
1035 return cls.query().filter(cls.follows_repo_id == repo_id)
1027
1036
1028 class CacheInvalidation(Base, BaseModel):
1037 class CacheInvalidation(Base, BaseModel):
1029 __tablename__ = 'cache_invalidation'
1038 __tablename__ = 'cache_invalidation'
1030 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1039 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1031 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1040 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1032 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1041 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1033 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1042 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1034 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1043 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1035
1044
1036
1045
1037 def __init__(self, cache_key, cache_args=''):
1046 def __init__(self, cache_key, cache_args=''):
1038 self.cache_key = cache_key
1047 self.cache_key = cache_key
1039 self.cache_args = cache_args
1048 self.cache_args = cache_args
1040 self.cache_active = False
1049 self.cache_active = False
1041
1050
1042 def __repr__(self):
1051 def __repr__(self):
1043 return "<%s('%s:%s')>" % (self.__class__.__name__,
1052 return "<%s('%s:%s')>" % (self.__class__.__name__,
1044 self.cache_id, self.cache_key)
1053 self.cache_id, self.cache_key)
1045
1054
1046 @classmethod
1055 @classmethod
1047 def invalidate(cls, key):
1056 def invalidate(cls, key):
1048 """
1057 """
1049 Returns Invalidation object if this given key should be invalidated
1058 Returns Invalidation object if this given key should be invalidated
1050 None otherwise. `cache_active = False` means that this cache
1059 None otherwise. `cache_active = False` means that this cache
1051 state is not valid and needs to be invalidated
1060 state is not valid and needs to be invalidated
1052
1061
1053 :param key:
1062 :param key:
1054 """
1063 """
1055 return cls.query()\
1064 return cls.query()\
1056 .filter(CacheInvalidation.cache_key == key)\
1065 .filter(CacheInvalidation.cache_key == key)\
1057 .filter(CacheInvalidation.cache_active == False)\
1066 .filter(CacheInvalidation.cache_active == False)\
1058 .scalar()
1067 .scalar()
1059
1068
1060 @classmethod
1069 @classmethod
1061 def set_invalidate(cls, key):
1070 def set_invalidate(cls, key):
1062 """
1071 """
1063 Mark this Cache key for invalidation
1072 Mark this Cache key for invalidation
1064
1073
1065 :param key:
1074 :param key:
1066 """
1075 """
1067
1076
1068 log.debug('marking %s for invalidation' % key)
1077 log.debug('marking %s for invalidation' % key)
1069 inv_obj = Session().query(cls)\
1078 inv_obj = Session().query(cls)\
1070 .filter(cls.cache_key == key).scalar()
1079 .filter(cls.cache_key == key).scalar()
1071 if inv_obj:
1080 if inv_obj:
1072 inv_obj.cache_active = False
1081 inv_obj.cache_active = False
1073 else:
1082 else:
1074 log.debug('cache key not found in invalidation db -> creating one')
1083 log.debug('cache key not found in invalidation db -> creating one')
1075 inv_obj = CacheInvalidation(key)
1084 inv_obj = CacheInvalidation(key)
1076
1085
1077 try:
1086 try:
1078 Session().add(inv_obj)
1087 Session().add(inv_obj)
1079 Session().commit()
1088 Session().commit()
1080 except Exception:
1089 except Exception:
1081 log.error(traceback.format_exc())
1090 log.error(traceback.format_exc())
1082 Session().rollback()
1091 Session().rollback()
1083
1092
1084 @classmethod
1093 @classmethod
1085 def set_valid(cls, key):
1094 def set_valid(cls, key):
1086 """
1095 """
1087 Mark this cache key as active and currently cached
1096 Mark this cache key as active and currently cached
1088
1097
1089 :param key:
1098 :param key:
1090 """
1099 """
1091 inv_obj = CacheInvalidation.query()\
1100 inv_obj = CacheInvalidation.query()\
1092 .filter(CacheInvalidation.cache_key == key).scalar()
1101 .filter(CacheInvalidation.cache_key == key).scalar()
1093 inv_obj.cache_active = True
1102 inv_obj.cache_active = True
1094 Session().add(inv_obj)
1103 Session().add(inv_obj)
1095 Session().commit()
1104 Session().commit()
1096
1105
1097
1106
1098 class ChangesetComment(Base, BaseModel):
1107 class ChangesetComment(Base, BaseModel):
1099 __tablename__ = 'changeset_comments'
1108 __tablename__ = 'changeset_comments'
1100 __table_args__ = ({'extend_existing':True},)
1109 __table_args__ = ({'extend_existing':True},)
1101 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1110 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1102 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1111 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1103 revision = Column('revision', String(40), nullable=False)
1112 revision = Column('revision', String(40), nullable=False)
1104 line_no = Column('line_no', Unicode(10), nullable=True)
1113 line_no = Column('line_no', Unicode(10), nullable=True)
1105 f_path = Column('f_path', Unicode(1000), nullable=True)
1114 f_path = Column('f_path', Unicode(1000), nullable=True)
1106 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1115 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1107 text = Column('text', Unicode(25000), nullable=False)
1116 text = Column('text', Unicode(25000), nullable=False)
1108 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1117 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1109
1118
1110 author = relationship('User', lazy='joined')
1119 author = relationship('User', lazy='joined')
1111 repo = relationship('Repository')
1120 repo = relationship('Repository')
1112
1121
1113
1122
1114 @classmethod
1123 @classmethod
1115 def get_users(cls, revision):
1124 def get_users(cls, revision):
1116 """
1125 """
1117 Returns user associated with this changesetComment. ie those
1126 Returns user associated with this changesetComment. ie those
1118 who actually commented
1127 who actually commented
1119
1128
1120 :param cls:
1129 :param cls:
1121 :param revision:
1130 :param revision:
1122 """
1131 """
1123 return Session().query(User)\
1132 return Session().query(User)\
1124 .filter(cls.revision == revision)\
1133 .filter(cls.revision == revision)\
1125 .join(ChangesetComment.author).all()
1134 .join(ChangesetComment.author).all()
1126
1135
1127
1136
1128 class Notification(Base, BaseModel):
1137 class Notification(Base, BaseModel):
1129 __tablename__ = 'notifications'
1138 __tablename__ = 'notifications'
1130 __table_args__ = ({'extend_existing':True})
1139 __table_args__ = ({'extend_existing':True})
1131
1140
1132 TYPE_CHANGESET_COMMENT = u'cs_comment'
1141 TYPE_CHANGESET_COMMENT = u'cs_comment'
1133 TYPE_MESSAGE = u'message'
1142 TYPE_MESSAGE = u'message'
1134 TYPE_MENTION = u'mention'
1143 TYPE_MENTION = u'mention'
1135
1144
1136 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1145 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1137 subject = Column('subject', Unicode(512), nullable=True)
1146 subject = Column('subject', Unicode(512), nullable=True)
1138 body = Column('body', Unicode(50000), nullable=True)
1147 body = Column('body', Unicode(50000), nullable=True)
1139 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1148 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1140 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1149 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1141 type_ = Column('type', Unicode(256))
1150 type_ = Column('type', Unicode(256))
1142
1151
1143 created_by_user = relationship('User')
1152 created_by_user = relationship('User')
1144 notifications_to_users = relationship('UserNotification',
1153 notifications_to_users = relationship('UserNotification',
1145 primaryjoin='Notification.notification_id==UserNotification.notification_id',
1154 primaryjoin='Notification.notification_id==UserNotification.notification_id',
1146 lazy='joined',
1155 lazy='joined',
1147 cascade="all, delete, delete-orphan")
1156 cascade="all, delete, delete-orphan")
1148
1157
1149 @property
1158 @property
1150 def recipients(self):
1159 def recipients(self):
1151 return [x.user for x in UserNotification.query()\
1160 return [x.user for x in UserNotification.query()\
1152 .filter(UserNotification.notification == self).all()]
1161 .filter(UserNotification.notification == self).all()]
1153
1162
1154 @classmethod
1163 @classmethod
1155 def create(cls, created_by, subject, body, recipients, type_=None):
1164 def create(cls, created_by, subject, body, recipients, type_=None):
1156 if type_ is None:
1165 if type_ is None:
1157 type_ = Notification.TYPE_MESSAGE
1166 type_ = Notification.TYPE_MESSAGE
1158
1167
1159 notification = cls()
1168 notification = cls()
1160 notification.created_by_user = created_by
1169 notification.created_by_user = created_by
1161 notification.subject = subject
1170 notification.subject = subject
1162 notification.body = body
1171 notification.body = body
1163 notification.type_ = type_
1172 notification.type_ = type_
1164
1173
1165 for u in recipients:
1174 for u in recipients:
1166 assoc = UserNotification()
1175 assoc = UserNotification()
1167 assoc.notification = notification
1176 assoc.notification = notification
1168 u.notifications.append(assoc)
1177 u.notifications.append(assoc)
1169 Session().add(notification)
1178 Session().add(notification)
1170 return notification
1179 return notification
1171
1180
1172 @property
1181 @property
1173 def description(self):
1182 def description(self):
1174 from rhodecode.model.notification import NotificationModel
1183 from rhodecode.model.notification import NotificationModel
1175 return NotificationModel().make_description(self)
1184 return NotificationModel().make_description(self)
1176
1185
1177 class UserNotification(Base, BaseModel):
1186 class UserNotification(Base, BaseModel):
1178 __tablename__ = 'user_to_notification'
1187 __tablename__ = 'user_to_notification'
1179 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1188 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1180 {'extend_existing':True})
1189 {'extend_existing':True})
1181 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1190 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1182 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1191 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1183 read = Column('read', Boolean, default=False)
1192 read = Column('read', Boolean, default=False)
1184 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1193 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1185
1194
1186 user = relationship('User', lazy="joined")
1195 user = relationship('User', lazy="joined")
1187 notification = relationship('Notification', lazy="joined", cascade='all')
1196 notification = relationship('Notification', lazy="joined", cascade='all')
1188
1197
1189 def mark_as_read(self):
1198 def mark_as_read(self):
1190 self.read = True
1199 self.read = True
1191 Session().add(self)
1200 Session().add(self)
1192
1201
1193 class DbMigrateVersion(Base, BaseModel):
1202 class DbMigrateVersion(Base, BaseModel):
1194 __tablename__ = 'db_migrate_version'
1203 __tablename__ = 'db_migrate_version'
1195 __table_args__ = {'extend_existing':True}
1204 __table_args__ = {'extend_existing':True}
1196 repository_id = Column('repository_id', String(250), primary_key=True)
1205 repository_id = Column('repository_id', String(250), primary_key=True)
1197 repository_path = Column('repository_path', Text)
1206 repository_path = Column('repository_path', Text)
1198 version = Column('version', Integer)
1207 version = Column('version', Integer)
1199
1208
@@ -1,143 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.lib.helpers import age
32 from rhodecode.lib.helpers import age
33
33
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import Notification, User, UserNotification
35 from rhodecode.model.db import Notification, User, UserNotification
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39 class NotificationModel(BaseModel):
40 class NotificationModel(BaseModel):
40
41
41
42
42 def __get_user(self, user):
43 def __get_user(self, user):
43 if isinstance(user, User):
44 if isinstance(user, basestring):
44 return user
45 elif isinstance(user, basestring):
46 return User.get_by_username(username=user)
45 return User.get_by_username(username=user)
47 elif isinstance(user, int):
48 return User.get(user)
49 else:
46 else:
50 raise Exception('Unsupported user must be one of int,'
47 return self._get_instance(User, user)
51 'str or User object')
52
48
53 def __get_notification(self, notification):
49 def __get_notification(self, notification):
54 if isinstance(notification, Notification):
50 if isinstance(notification, Notification):
55 return notification
51 return notification
56 elif isinstance(notification, int):
52 elif isinstance(notification, int):
57 return Notification.get(notification)
53 return Notification.get(notification)
58 else:
54 else:
59 if notification:
55 if notification:
60 raise Exception('notification must be int or Instance'
56 raise Exception('notification must be int or Instance'
61 ' of Notification got %s' % type(notification))
57 ' of Notification got %s' % type(notification))
62
58
63
59
64 def create(self, created_by, subject, body, recipients,
60 def create(self, created_by, subject, body, recipients,
65 type_=Notification.TYPE_MESSAGE):
61 type_=Notification.TYPE_MESSAGE):
66 """
62 """
67
63
68 Creates notification of given type
64 Creates notification of given type
69
65
70 :param created_by: int, str or User instance. User who created this
66 :param created_by: int, str or User instance. User who created this
71 notification
67 notification
72 :param subject:
68 :param subject:
73 :param body:
69 :param body:
74 :param recipients: list of int, str or User objects
70 :param recipients: list of int, str or User objects
75 :param type_: type of notification
71 :param type_: type of notification
76 """
72 """
77
73
78 if not getattr(recipients, '__iter__', False):
74 if not getattr(recipients, '__iter__', False):
79 raise Exception('recipients must be a list of iterable')
75 raise Exception('recipients must be a list of iterable')
80
76
81 created_by_obj = self.__get_user(created_by)
77 created_by_obj = self.__get_user(created_by)
82
78
83 recipients_objs = []
79 recipients_objs = []
84 for u in recipients:
80 for u in recipients:
85 recipients_objs.append(self.__get_user(u))
81 obj = self.__get_user(u)
82 if obj:
83 recipients_objs.append(obj)
86 recipients_objs = set(recipients_objs)
84 recipients_objs = set(recipients_objs)
87 return Notification.create(created_by=created_by_obj, subject=subject,
85 return Notification.create(created_by=created_by_obj, subject=subject,
88 body=body, recipients=recipients_objs,
86 body=body, recipients=recipients_objs,
89 type_=type_)
87 type_=type_)
90
88
91 def delete(self, user, notification):
89 def delete(self, user, notification):
92 # we don't want to remove actual notification just the assignment
90 # we don't want to remove actual notification just the assignment
93 try:
91 try:
94 notification = self.__get_notification(notification)
92 notification = self.__get_notification(notification)
95 user = self.__get_user(user)
93 user = self.__get_user(user)
96 if notification and user:
94 if notification and user:
97 obj = UserNotification.query().filter(UserNotification.user == user)\
95 obj = UserNotification.query().filter(UserNotification.user == user)\
98 .filter(UserNotification.notification == notification).one()
96 .filter(UserNotification.notification == notification).one()
99 self.sa.delete(obj)
97 self.sa.delete(obj)
100 return True
98 return True
101 except Exception:
99 except Exception:
102 log.error(traceback.format_exc())
100 log.error(traceback.format_exc())
103 raise
101 raise
104
102
105 def get_for_user(self, user):
103 def get_for_user(self, user):
106 user = self.__get_user(user)
104 user = self.__get_user(user)
107 return user.notifications
105 return user.notifications
108
106
109 def get_unread_cnt_for_user(self, user):
107 def get_unread_cnt_for_user(self, user):
110 user = self.__get_user(user)
108 user = self.__get_user(user)
111 return UserNotification.query()\
109 return UserNotification.query()\
112 .filter(UserNotification.read == False)\
110 .filter(UserNotification.read == False)\
113 .filter(UserNotification.user == user).count()
111 .filter(UserNotification.user == user).count()
114
112
115 def get_unread_for_user(self, user):
113 def get_unread_for_user(self, user):
116 user = self.__get_user(user)
114 user = self.__get_user(user)
117 return [x.notification for x in UserNotification.query()\
115 return [x.notification for x in UserNotification.query()\
118 .filter(UserNotification.read == False)\
116 .filter(UserNotification.read == False)\
119 .filter(UserNotification.user == user).all()]
117 .filter(UserNotification.user == user).all()]
120
118
121 def get_user_notification(self, user, notification):
119 def get_user_notification(self, user, notification):
122 user = self.__get_user(user)
120 user = self.__get_user(user)
123 notification = self.__get_notification(notification)
121 notification = self.__get_notification(notification)
124
122
125 return UserNotification.query()\
123 return UserNotification.query()\
126 .filter(UserNotification.notification == notification)\
124 .filter(UserNotification.notification == notification)\
127 .filter(UserNotification.user == user).scalar()
125 .filter(UserNotification.user == user).scalar()
128
126
129 def make_description(self, notification):
127 def make_description(self, notification):
130 """
128 """
131 Creates a human readable description based on properties
129 Creates a human readable description based on properties
132 of notification object
130 of notification object
133 """
131 """
134
132
135 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
133 _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
136 notification.TYPE_MESSAGE:_('sent message'),
134 notification.TYPE_MESSAGE:_('sent message'),
137 notification.TYPE_MENTION:_('mentioned you')}
135 notification.TYPE_MENTION:_('mentioned you')}
138
136
139 tmpl = "%(user)s %(action)s %(when)s"
137 tmpl = "%(user)s %(action)s %(when)s"
140 data = dict(user=notification.created_by_user.username,
138 data = dict(user=notification.created_by_user.username,
141 action=_map[notification.type_],
139 action=_map[notification.type_],
142 when=age(notification.created_on))
140 when=age(notification.created_on))
143 return tmpl % data
141 return tmpl % data
@@ -1,115 +1,117 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.permission
3 rhodecode.model.permission
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 permissions model for RhodeCode
6 permissions model for RhodeCode
7
7
8 :created_on: Aug 20, 2010
8 :created_on: Aug 20, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from sqlalchemy.exc import DatabaseError
29 from sqlalchemy.exc import DatabaseError
30
30
31 from rhodecode.lib.caching_query import FromCache
31 from rhodecode.lib.caching_query import FromCache
32
32
33 from rhodecode.model import BaseModel
33 from rhodecode.model import BaseModel
34 from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm
34 from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm
35
35
36
37 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
38
37
39
38
40 class PermissionModel(BaseModel):
39 class PermissionModel(BaseModel):
41 """Permissions model for RhodeCode
40 """
41 Permissions model for RhodeCode
42 """
42 """
43
43
44 def get_permission(self, permission_id, cache=False):
44 def get_permission(self, permission_id, cache=False):
45 """Get's permissions by id
45 """
46 Get's permissions by id
46
47
47 :param permission_id: id of permission to get from database
48 :param permission_id: id of permission to get from database
48 :param cache: use Cache for this query
49 :param cache: use Cache for this query
49 """
50 """
50 perm = self.sa.query(Permission)
51 perm = self.sa.query(Permission)
51 if cache:
52 if cache:
52 perm = perm.options(FromCache("sql_cache_short",
53 perm = perm.options(FromCache("sql_cache_short",
53 "get_permission_%s" % permission_id))
54 "get_permission_%s" % permission_id))
54 return perm.get(permission_id)
55 return perm.get(permission_id)
55
56
56 def get_permission_by_name(self, name, cache=False):
57 def get_permission_by_name(self, name, cache=False):
57 """Get's permissions by given name
58 """
59 Get's permissions by given name
58
60
59 :param name: name to fetch
61 :param name: name to fetch
60 :param cache: Use cache for this query
62 :param cache: Use cache for this query
61 """
63 """
62 perm = self.sa.query(Permission)\
64 perm = self.sa.query(Permission)\
63 .filter(Permission.permission_name == name)
65 .filter(Permission.permission_name == name)
64 if cache:
66 if cache:
65 perm = perm.options(FromCache("sql_cache_short",
67 perm = perm.options(FromCache("sql_cache_short",
66 "get_permission_%s" % name))
68 "get_permission_%s" % name))
67 return perm.scalar()
69 return perm.scalar()
68
70
69 def update(self, form_result):
71 def update(self, form_result):
70 perm_user = self.sa.query(User)\
72 perm_user = self.sa.query(User)\
71 .filter(User.username ==
73 .filter(User.username ==
72 form_result['perm_user_name']).scalar()
74 form_result['perm_user_name']).scalar()
73 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
75 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
74 perm_user).all()
76 perm_user).all()
75 if len(u2p) != 3:
77 if len(u2p) != 3:
76 raise Exception('Defined: %s should be 3 permissions for default'
78 raise Exception('Defined: %s should be 3 permissions for default'
77 ' user. This should not happen please verify'
79 ' user. This should not happen please verify'
78 ' your database' % len(u2p))
80 ' your database' % len(u2p))
79
81
80 try:
82 try:
81 #stage 1 change defaults
83 # stage 1 change defaults
82 for p in u2p:
84 for p in u2p:
83 if p.permission.permission_name.startswith('repository.'):
85 if p.permission.permission_name.startswith('repository.'):
84 p.permission = self.get_permission_by_name(
86 p.permission = self.get_permission_by_name(
85 form_result['default_perm'])
87 form_result['default_perm'])
86 self.sa.add(p)
88 self.sa.add(p)
87
89
88 if p.permission.permission_name.startswith('hg.register.'):
90 if p.permission.permission_name.startswith('hg.register.'):
89 p.permission = self.get_permission_by_name(
91 p.permission = self.get_permission_by_name(
90 form_result['default_register'])
92 form_result['default_register'])
91 self.sa.add(p)
93 self.sa.add(p)
92
94
93 if p.permission.permission_name.startswith('hg.create.'):
95 if p.permission.permission_name.startswith('hg.create.'):
94 p.permission = self.get_permission_by_name(
96 p.permission = self.get_permission_by_name(
95 form_result['default_create'])
97 form_result['default_create'])
96 self.sa.add(p)
98 self.sa.add(p)
97
99
98 #stage 2 update all default permissions for repos if checked
100 #stage 2 update all default permissions for repos if checked
99 if form_result['overwrite_default'] == True:
101 if form_result['overwrite_default'] == True:
100 for r2p in self.sa.query(UserRepoToPerm)\
102 for r2p in self.sa.query(UserRepoToPerm)\
101 .filter(UserRepoToPerm.user == perm_user).all():
103 .filter(UserRepoToPerm.user == perm_user).all():
102 r2p.permission = self.get_permission_by_name(
104 r2p.permission = self.get_permission_by_name(
103 form_result['default_perm'])
105 form_result['default_perm'])
104 self.sa.add(r2p)
106 self.sa.add(r2p)
105
107
106 #stage 3 set anonymous access
108 # stage 3 set anonymous access
107 if perm_user.username == 'default':
109 if perm_user.username == 'default':
108 perm_user.active = bool(form_result['anonymous'])
110 perm_user.active = bool(form_result['anonymous'])
109 self.sa.add(perm_user)
111 self.sa.add(perm_user)
110
112
111 self.sa.commit()
113 self.sa.commit()
112 except (DatabaseError,):
114 except (DatabaseError,):
113 log.error(traceback.format_exc())
115 log.error(traceback.format_exc())
114 self.sa.rollback()
116 self.sa.rollback()
115 raise
117 raise
@@ -1,414 +1,414 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from sqlalchemy.orm import joinedload, make_transient
32
33 from vcs.utils.lazy import LazyProperty
31 from vcs.utils.lazy import LazyProperty
34 from vcs.backends import get_backend
32 from vcs.backends import get_backend
35
33
36 from rhodecode.lib import safe_str
34 from rhodecode.lib import safe_str
37 from rhodecode.lib.caching_query import FromCache
35 from rhodecode.lib.caching_query import FromCache
38
36
39 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
38 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
42 from rhodecode.model.user import UserModel
43
40
44 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
45
42
46
43
47 class RepoModel(BaseModel):
44 class RepoModel(BaseModel):
48
45
49 @LazyProperty
46 @LazyProperty
50 def repos_path(self):
47 def repos_path(self):
51 """Get's the repositories root path from database
48 """
49 Get's the repositories root path from database
52 """
50 """
53
51
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
52 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 return q.ui_value
53 return q.ui_value
56
54
57 def get(self, repo_id, cache=False):
55 def get(self, repo_id, cache=False):
58 repo = self.sa.query(Repository)\
56 repo = self.sa.query(Repository)\
59 .filter(Repository.repo_id == repo_id)
57 .filter(Repository.repo_id == repo_id)
60
58
61 if cache:
59 if cache:
62 repo = repo.options(FromCache("sql_cache_short",
60 repo = repo.options(FromCache("sql_cache_short",
63 "get_repo_%s" % repo_id))
61 "get_repo_%s" % repo_id))
64 return repo.scalar()
62 return repo.scalar()
65
63
66 def get_by_repo_name(self, repo_name, cache=False):
64 def get_by_repo_name(self, repo_name, cache=False):
67 repo = self.sa.query(Repository)\
65 repo = self.sa.query(Repository)\
68 .filter(Repository.repo_name == repo_name)
66 .filter(Repository.repo_name == repo_name)
69
67
70 if cache:
68 if cache:
71 repo = repo.options(FromCache("sql_cache_short",
69 repo = repo.options(FromCache("sql_cache_short",
72 "get_repo_%s" % repo_name))
70 "get_repo_%s" % repo_name))
73 return repo.scalar()
71 return repo.scalar()
74
72
75
73
76 def get_users_js(self):
74 def get_users_js(self):
77
75
78 users = self.sa.query(User).filter(User.active == True).all()
76 users = self.sa.query(User).filter(User.active == True).all()
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
77 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
78 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
81 u.lastname, u.username)
79 u.lastname, u.username)
82 for u in users])
80 for u in users])
83 return users_array
81 return users_array
84
82
85 def get_users_groups_js(self):
83 def get_users_groups_js(self):
86 users_groups = self.sa.query(UsersGroup)\
84 users_groups = self.sa.query(UsersGroup)\
87 .filter(UsersGroup.users_group_active == True).all()
85 .filter(UsersGroup.users_group_active == True).all()
88
86
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
87 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
90
88
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
89 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
92 (gr.users_group_id, gr.users_group_name,
90 (gr.users_group_id, gr.users_group_name,
93 len(gr.members))
91 len(gr.members))
94 for gr in users_groups])
92 for gr in users_groups])
95 return users_groups_array
93 return users_groups_array
96
94
97 def _get_defaults(self, repo_name):
95 def _get_defaults(self, repo_name):
98 """
96 """
99 Get's information about repository, and returns a dict for
97 Get's information about repository, and returns a dict for
100 usage in forms
98 usage in forms
101
99
102 :param repo_name:
100 :param repo_name:
103 """
101 """
104
102
105 repo_info = Repository.get_by_repo_name(repo_name)
103 repo_info = Repository.get_by_repo_name(repo_name)
106
104
107 if repo_info is None:
105 if repo_info is None:
108 return None
106 return None
109
107
110 defaults = repo_info.get_dict()
108 defaults = repo_info.get_dict()
111 group, repo_name = repo_info.groups_and_repo
109 group, repo_name = repo_info.groups_and_repo
112 defaults['repo_name'] = repo_name
110 defaults['repo_name'] = repo_name
113 defaults['repo_group'] = getattr(group[-1] if group else None,
111 defaults['repo_group'] = getattr(group[-1] if group else None,
114 'group_id', None)
112 'group_id', None)
115
113
116 # fill owner
114 # fill owner
117 if repo_info.user:
115 if repo_info.user:
118 defaults.update({'user': repo_info.user.username})
116 defaults.update({'user': repo_info.user.username})
119 else:
117 else:
120 replacement_user = User.query().filter(User.admin ==
118 replacement_user = User.query().filter(User.admin ==
121 True).first().username
119 True).first().username
122 defaults.update({'user': replacement_user})
120 defaults.update({'user': replacement_user})
123
121
124 # fill repository users
122 # fill repository users
125 for p in repo_info.repo_to_perm:
123 for p in repo_info.repo_to_perm:
126 defaults.update({'u_perm_%s' % p.user.username:
124 defaults.update({'u_perm_%s' % p.user.username:
127 p.permission.permission_name})
125 p.permission.permission_name})
128
126
129 # fill repository groups
127 # fill repository groups
130 for p in repo_info.users_group_to_perm:
128 for p in repo_info.users_group_to_perm:
131 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
129 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
132 p.permission.permission_name})
130 p.permission.permission_name})
133
131
134 return defaults
132 return defaults
135
133
136
134
137 def update(self, repo_name, form_data):
135 def update(self, repo_name, form_data):
138 try:
136 try:
139 cur_repo = self.get_by_repo_name(repo_name, cache=False)
137 cur_repo = self.get_by_repo_name(repo_name, cache=False)
140
138
141 # update permissions
139 # update permissions
142 for member, perm, member_type in form_data['perms_updates']:
140 for member, perm, member_type in form_data['perms_updates']:
143 if member_type == 'user':
141 if member_type == 'user':
142 _member = User.get_by_username(member)
144 r2p = self.sa.query(UserRepoToPerm)\
143 r2p = self.sa.query(UserRepoToPerm)\
145 .filter(UserRepoToPerm.user == User.get_by_username(member))\
144 .filter(UserRepoToPerm.user == _member)\
146 .filter(UserRepoToPerm.repository == cur_repo)\
145 .filter(UserRepoToPerm.repository == cur_repo)\
147 .one()
146 .one()
148
147
149 r2p.permission = self.sa.query(Permission)\
148 r2p.permission = self.sa.query(Permission)\
150 .filter(Permission.permission_name ==
149 .filter(Permission.permission_name ==
151 perm).scalar()
150 perm).scalar()
152 self.sa.add(r2p)
151 self.sa.add(r2p)
153 else:
152 else:
154 g2p = self.sa.query(UsersGroupRepoToPerm)\
153 g2p = self.sa.query(UsersGroupRepoToPerm)\
155 .filter(UsersGroupRepoToPerm.users_group ==
154 .filter(UsersGroupRepoToPerm.users_group ==
156 UsersGroup.get_by_group_name(member))\
155 UsersGroup.get_by_group_name(member))\
157 .filter(UsersGroupRepoToPerm.repository ==
156 .filter(UsersGroupRepoToPerm.repository ==
158 cur_repo).one()
157 cur_repo).one()
159
158
160 g2p.permission = self.sa.query(Permission)\
159 g2p.permission = self.sa.query(Permission)\
161 .filter(Permission.permission_name ==
160 .filter(Permission.permission_name ==
162 perm).scalar()
161 perm).scalar()
163 self.sa.add(g2p)
162 self.sa.add(g2p)
164
163
165 # set new permissions
164 # set new permissions
166 for member, perm, member_type in form_data['perms_new']:
165 for member, perm, member_type in form_data['perms_new']:
167 if member_type == 'user':
166 if member_type == 'user':
168 r2p = UserRepoToPerm()
167 r2p = UserRepoToPerm()
169 r2p.repository = cur_repo
168 r2p.repository = cur_repo
170 r2p.user = User.get_by_username(member)
169 r2p.user = User.get_by_username(member)
171
170
172 r2p.permission = self.sa.query(Permission)\
171 r2p.permission = self.sa.query(Permission)\
173 .filter(Permission.
172 .filter(Permission.
174 permission_name == perm)\
173 permission_name == perm)\
175 .scalar()
174 .scalar()
176 self.sa.add(r2p)
175 self.sa.add(r2p)
177 else:
176 else:
178 g2p = UsersGroupRepoToPerm()
177 g2p = UsersGroupRepoToPerm()
179 g2p.repository = cur_repo
178 g2p.repository = cur_repo
180 g2p.users_group = UsersGroup.get_by_group_name(member)
179 g2p.users_group = UsersGroup.get_by_group_name(member)
181 g2p.permission = self.sa.query(Permission)\
180 g2p.permission = self.sa.query(Permission)\
182 .filter(Permission.
181 .filter(Permission.
183 permission_name == perm)\
182 permission_name == perm)\
184 .scalar()
183 .scalar()
185 self.sa.add(g2p)
184 self.sa.add(g2p)
186
185
187 # update current repo
186 # update current repo
188 for k, v in form_data.items():
187 for k, v in form_data.items():
189 if k == 'user':
188 if k == 'user':
190 cur_repo.user = User.get_by_username(v)
189 cur_repo.user = User.get_by_username(v)
191 elif k == 'repo_name':
190 elif k == 'repo_name':
192 pass
191 pass
193 elif k == 'repo_group':
192 elif k == 'repo_group':
194 cur_repo.group = RepoGroup.get(v)
193 cur_repo.group = RepoGroup.get(v)
195
194
196 else:
195 else:
197 setattr(cur_repo, k, v)
196 setattr(cur_repo, k, v)
198
197
199 new_name = cur_repo.get_new_name(form_data['repo_name'])
198 new_name = cur_repo.get_new_name(form_data['repo_name'])
200 cur_repo.repo_name = new_name
199 cur_repo.repo_name = new_name
201
200
202 self.sa.add(cur_repo)
201 self.sa.add(cur_repo)
203
202
204 if repo_name != new_name:
203 if repo_name != new_name:
205 # rename repository
204 # rename repository
206 self.__rename_repo(old=repo_name, new=new_name)
205 self.__rename_repo(old=repo_name, new=new_name)
207
206
208 self.sa.commit()
207 self.sa.commit()
209 return cur_repo
208 return cur_repo
210 except:
209 except:
211 log.error(traceback.format_exc())
210 log.error(traceback.format_exc())
212 self.sa.rollback()
211 self.sa.rollback()
213 raise
212 raise
214
213
215 def create(self, form_data, cur_user, just_db=False, fork=False):
214 def create(self, form_data, cur_user, just_db=False, fork=False):
216
215
217 try:
216 try:
218 if fork:
217 if fork:
219 repo_name = form_data['fork_name']
218 repo_name = form_data['fork_name']
220 org_name = form_data['repo_name']
219 org_name = form_data['repo_name']
221 org_full_name = org_name
220 org_full_name = org_name
222
221
223 else:
222 else:
224 org_name = repo_name = form_data['repo_name']
223 org_name = repo_name = form_data['repo_name']
225 repo_name_full = form_data['repo_name_full']
224 repo_name_full = form_data['repo_name_full']
226
225
227 new_repo = Repository()
226 new_repo = Repository()
228 new_repo.enable_statistics = False
227 new_repo.enable_statistics = False
229 for k, v in form_data.items():
228 for k, v in form_data.items():
230 if k == 'repo_name':
229 if k == 'repo_name':
231 if fork:
230 if fork:
232 v = repo_name
231 v = repo_name
233 else:
232 else:
234 v = repo_name_full
233 v = repo_name_full
235 if k == 'repo_group':
234 if k == 'repo_group':
236 k = 'group_id'
235 k = 'group_id'
237
236
238 if k == 'description':
237 if k == 'description':
239 v = v or repo_name
238 v = v or repo_name
240
239
241 setattr(new_repo, k, v)
240 setattr(new_repo, k, v)
242
241
243 if fork:
242 if fork:
244 parent_repo = self.sa.query(Repository)\
243 parent_repo = self.sa.query(Repository)\
245 .filter(Repository.repo_name == org_full_name).one()
244 .filter(Repository.repo_name == org_full_name).one()
246 new_repo.fork = parent_repo
245 new_repo.fork = parent_repo
247
246
248 new_repo.user_id = cur_user.user_id
247 new_repo.user_id = cur_user.user_id
249 self.sa.add(new_repo)
248 self.sa.add(new_repo)
250
249
251 #create default permission
250 #create default permission
252 repo_to_perm = UserRepoToPerm()
251 repo_to_perm = UserRepoToPerm()
253 default = 'repository.read'
252 default = 'repository.read'
254 for p in User.get_by_username('default').user_perms:
253 for p in User.get_by_username('default').user_perms:
255 if p.permission.permission_name.startswith('repository.'):
254 if p.permission.permission_name.startswith('repository.'):
256 default = p.permission.permission_name
255 default = p.permission.permission_name
257 break
256 break
258
257
259 default_perm = 'repository.none' if form_data['private'] else default
258 default_perm = 'repository.none' if form_data['private'] else default
260
259
261 repo_to_perm.permission_id = self.sa.query(Permission)\
260 repo_to_perm.permission_id = self.sa.query(Permission)\
262 .filter(Permission.permission_name == default_perm)\
261 .filter(Permission.permission_name == default_perm)\
263 .one().permission_id
262 .one().permission_id
264
263
265 repo_to_perm.repository = new_repo
264 repo_to_perm.repository = new_repo
266 repo_to_perm.user_id = User.get_by_username('default').user_id
265 repo_to_perm.user_id = User.get_by_username('default').user_id
267
266
268 self.sa.add(repo_to_perm)
267 self.sa.add(repo_to_perm)
269
268
270 if not just_db:
269 if not just_db:
271 self.__create_repo(repo_name, form_data['repo_type'],
270 self.__create_repo(repo_name, form_data['repo_type'],
272 form_data['repo_group'],
271 form_data['repo_group'],
273 form_data['clone_uri'])
272 form_data['clone_uri'])
274
273
275 self.sa.commit()
274 self.sa.commit()
276
275
277 #now automatically start following this repository as owner
276 #now automatically start following this repository as owner
278 from rhodecode.model.scm import ScmModel
277 from rhodecode.model.scm import ScmModel
279 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
278 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
280 cur_user.user_id)
279 cur_user.user_id)
281 return new_repo
280 return new_repo
282 except:
281 except:
283 log.error(traceback.format_exc())
282 log.error(traceback.format_exc())
284 self.sa.rollback()
283 self.sa.rollback()
285 raise
284 raise
286
285
287 def create_fork(self, form_data, cur_user):
286 def create_fork(self, form_data, cur_user):
288 from rhodecode.lib.celerylib import tasks, run_task
287 from rhodecode.lib.celerylib import tasks, run_task
289 run_task(tasks.create_repo_fork, form_data, cur_user)
288 run_task(tasks.create_repo_fork, form_data, cur_user)
290
289
291 def delete(self, repo):
290 def delete(self, repo):
292 try:
291 try:
293 self.sa.delete(repo)
292 self.sa.delete(repo)
294 self.__delete_repo(repo)
293 self.__delete_repo(repo)
295 self.sa.commit()
294 self.sa.commit()
296 except:
295 except:
297 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
298 self.sa.rollback()
297 self.sa.rollback()
299 raise
298 raise
300
299
301 def delete_perm_user(self, form_data, repo_name):
300 def delete_perm_user(self, form_data, repo_name):
302 try:
301 try:
303 obj = self.sa.query(UserRepoToPerm)\
302 obj = self.sa.query(UserRepoToPerm)\
304 .filter(UserRepoToPerm.repository \
303 .filter(UserRepoToPerm.repository \
305 == self.get_by_repo_name(repo_name))\
304 == self.get_by_repo_name(repo_name))\
306 .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
305 .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
307 self.sa.delete(obj)
306 self.sa.delete(obj)
308 self.sa.commit()
307 self.sa.commit()
309 except:
308 except:
310 log.error(traceback.format_exc())
309 log.error(traceback.format_exc())
311 self.sa.rollback()
310 self.sa.rollback()
312 raise
311 raise
313
312
314 def delete_perm_users_group(self, form_data, repo_name):
313 def delete_perm_users_group(self, form_data, repo_name):
315 try:
314 try:
316 obj = self.sa.query(UsersGroupRepoToPerm)\
315 obj = self.sa.query(UsersGroupRepoToPerm)\
317 .filter(UsersGroupRepoToPerm.repository \
316 .filter(UsersGroupRepoToPerm.repository \
318 == self.get_by_repo_name(repo_name))\
317 == self.get_by_repo_name(repo_name))\
319 .filter(UsersGroupRepoToPerm.users_group_id \
318 .filter(UsersGroupRepoToPerm.users_group_id
320 == form_data['users_group_id']).one()
319 == form_data['users_group_id']).one()
321 self.sa.delete(obj)
320 self.sa.delete(obj)
322 self.sa.commit()
321 self.sa.commit()
323 except:
322 except:
324 log.error(traceback.format_exc())
323 log.error(traceback.format_exc())
325 self.sa.rollback()
324 self.sa.rollback()
326 raise
325 raise
327
326
328 def delete_stats(self, repo_name):
327 def delete_stats(self, repo_name):
329 try:
328 try:
330 obj = self.sa.query(Statistics)\
329 obj = self.sa.query(Statistics)\
331 .filter(Statistics.repository == \
330 .filter(Statistics.repository == \
332 self.get_by_repo_name(repo_name)).one()
331 self.get_by_repo_name(repo_name)).one()
333 self.sa.delete(obj)
332 self.sa.delete(obj)
334 self.sa.commit()
333 self.sa.commit()
335 except:
334 except:
336 log.error(traceback.format_exc())
335 log.error(traceback.format_exc())
337 self.sa.rollback()
336 self.sa.rollback()
338 raise
337 raise
339
338
340 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
339 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
341 """
340 """
342 makes repository on filesystem. It's group aware means it'll create
341 makes repository on filesystem. It's group aware means it'll create
343 a repository within a group, and alter the paths accordingly of
342 a repository within a group, and alter the paths accordingly of
344 group location
343 group location
345
344
346 :param repo_name:
345 :param repo_name:
347 :param alias:
346 :param alias:
348 :param parent_id:
347 :param parent_id:
349 :param clone_uri:
348 :param clone_uri:
350 """
349 """
351 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
350 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
352
351
353 if new_parent_id:
352 if new_parent_id:
354 paths = RepoGroup.get(new_parent_id).full_path.split(RepoGroup.url_sep())
353 paths = RepoGroup.get(new_parent_id)\
354 .full_path.split(RepoGroup.url_sep())
355 new_parent_path = os.sep.join(paths)
355 new_parent_path = os.sep.join(paths)
356 else:
356 else:
357 new_parent_path = ''
357 new_parent_path = ''
358
358
359 repo_path = os.path.join(*map(lambda x:safe_str(x),
359 repo_path = os.path.join(*map(lambda x:safe_str(x),
360 [self.repos_path, new_parent_path, repo_name]))
360 [self.repos_path, new_parent_path, repo_name]))
361
361
362
362
363 # check if this path is not a repository
363 # check if this path is not a repository
364 if is_valid_repo(repo_path, self.repos_path):
364 if is_valid_repo(repo_path, self.repos_path):
365 raise Exception('This path %s is a valid repository' % repo_path)
365 raise Exception('This path %s is a valid repository' % repo_path)
366
366
367 # check if this path is a group
367 # check if this path is a group
368 if is_valid_repos_group(repo_path, self.repos_path):
368 if is_valid_repos_group(repo_path, self.repos_path):
369 raise Exception('This path %s is a valid group' % repo_path)
369 raise Exception('This path %s is a valid group' % repo_path)
370
370
371 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
371 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
372 clone_uri)
372 clone_uri)
373 backend = get_backend(alias)
373 backend = get_backend(alias)
374
374
375 backend(repo_path, create=True, src_url=clone_uri)
375 backend(repo_path, create=True, src_url=clone_uri)
376
376
377
377
378 def __rename_repo(self, old, new):
378 def __rename_repo(self, old, new):
379 """
379 """
380 renames repository on filesystem
380 renames repository on filesystem
381
381
382 :param old: old name
382 :param old: old name
383 :param new: new name
383 :param new: new name
384 """
384 """
385 log.info('renaming repo from %s to %s', old, new)
385 log.info('renaming repo from %s to %s', old, new)
386
386
387 old_path = os.path.join(self.repos_path, old)
387 old_path = os.path.join(self.repos_path, old)
388 new_path = os.path.join(self.repos_path, new)
388 new_path = os.path.join(self.repos_path, new)
389 if os.path.isdir(new_path):
389 if os.path.isdir(new_path):
390 raise Exception('Was trying to rename to already existing dir %s' \
390 raise Exception('Was trying to rename to already existing dir %s' \
391 % new_path)
391 % new_path)
392 shutil.move(old_path, new_path)
392 shutil.move(old_path, new_path)
393
393
394 def __delete_repo(self, repo):
394 def __delete_repo(self, repo):
395 """
395 """
396 removes repo from filesystem, the removal is acctually made by
396 removes repo from filesystem, the removal is acctually made by
397 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
397 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
398 repository is no longer valid for rhodecode, can be undeleted later on
398 repository is no longer valid for rhodecode, can be undeleted later on
399 by reverting the renames on this repository
399 by reverting the renames on this repository
400
400
401 :param repo: repo object
401 :param repo: repo object
402 """
402 """
403 rm_path = os.path.join(self.repos_path, repo.repo_name)
403 rm_path = os.path.join(self.repos_path, repo.repo_name)
404 log.info("Removing %s", rm_path)
404 log.info("Removing %s", rm_path)
405 #disable hg/git
405 #disable hg/git
406 alias = repo.repo_type
406 alias = repo.repo_type
407 shutil.move(os.path.join(rm_path, '.%s' % alias),
407 shutil.move(os.path.join(rm_path, '.%s' % alias),
408 os.path.join(rm_path, 'rm__.%s' % alias))
408 os.path.join(rm_path, 'rm__.%s' % alias))
409 #disable repo
409 #disable repo
410 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
410 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
411 % (datetime.today()\
411 % (datetime.today()\
412 .strftime('%Y%m%d_%H%M%S_%f'),
412 .strftime('%Y%m%d_%H%M%S_%f'),
413 repo.repo_name)))
413 repo.repo_name)))
414
414
@@ -1,63 +1,65 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.users_group
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 repository permission model for RhodeCode
6 repository permission model for RhodeCode
7
7
8 :created_on: Oct 1, 2011
8 :created_on: Oct 1, 2011
9 :author: nvinot
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 from rhodecode.model.db import BaseModel, UserRepoToPerm, Permission
27 from rhodecode.model import BaseModel
28 from rhodecode.model.meta import Session
28 from rhodecode.model.db import UserRepoToPerm, Permission
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32 class RepositoryPermissionModel(BaseModel):
33 class RepositoryPermissionModel(BaseModel):
34
33 def get_user_permission(self, repository, user):
35 def get_user_permission(self, repository, user):
34 return UserRepoToPerm.query() \
36 return UserRepoToPerm.query() \
35 .filter(UserRepoToPerm.user == user) \
37 .filter(UserRepoToPerm.user == user) \
36 .filter(UserRepoToPerm.repository == repository) \
38 .filter(UserRepoToPerm.repository == repository) \
37 .scalar()
39 .scalar()
38
40
39 def update_user_permission(self, repository, user, permission):
41 def update_user_permission(self, repository, user, permission):
40 permission = Permission.get_by_key(permission)
42 permission = Permission.get_by_key(permission)
41 current = self.get_user_permission(repository, user)
43 current = self.get_user_permission(repository, user)
42 if current:
44 if current:
43 if not current.permission is permission:
45 if not current.permission is permission:
44 current.permission = permission
46 current.permission = permission
45 else:
47 else:
46 p = UserRepoToPerm()
48 p = UserRepoToPerm()
47 p.user = user
49 p.user = user
48 p.repository = repository
50 p.repository = repository
49 p.permission = permission
51 p.permission = permission
50 Session.add(p)
52 self.sa.add(p)
51 Session.commit()
53 self.sa.commit()
52
54
53 def delete_user_permission(self, repository, user):
55 def delete_user_permission(self, repository, user):
54 current = self.get_user_permission(repository, user)
56 current = self.get_user_permission(repository, user)
55 if current:
57 if current:
56 Session.delete(current)
58 self.sa.delete(current)
57 Session.commit()
59 self.sa.commit()
58
60
59 def update_or_delete_user_permission(self, repository, user, permission):
61 def update_or_delete_user_permission(self, repository, user, permission):
60 if permission:
62 if permission:
61 self.update_user_permission(repository, user, permission)
63 self.update_user_permission(repository, user, permission)
62 else:
64 else:
63 self.delete_user_permission(repository, user)
65 self.delete_user_permission(repository, user)
@@ -1,383 +1,384 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29
29
30 from sqlalchemy.exc import DatabaseError
31
32 from vcs import get_backend
30 from vcs import get_backend
33 from vcs.exceptions import RepositoryError
31 from vcs.exceptions import RepositoryError
34 from vcs.utils.lazy import LazyProperty
32 from vcs.utils.lazy import LazyProperty
35 from vcs.nodes import FileNode
33 from vcs.nodes import FileNode
36
34
37 from rhodecode import BACKENDS
35 from rhodecode import BACKENDS
38 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
39 from rhodecode.lib import safe_str
37 from rhodecode.lib import safe_str
40 from rhodecode.lib.auth import HasRepoPermissionAny
38 from rhodecode.lib.auth import HasRepoPermissionAny
41 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
39 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
42 action_logger, EmptyChangeset
40 action_logger, EmptyChangeset
43 from rhodecode.model import BaseModel
41 from rhodecode.model import BaseModel
44 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
42 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
45 UserFollowing, UserLog, User
43 UserFollowing, UserLog, User
46
44
47 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
48
46
49
47
50 class UserTemp(object):
48 class UserTemp(object):
51 def __init__(self, user_id):
49 def __init__(self, user_id):
52 self.user_id = user_id
50 self.user_id = user_id
53
51
54 def __repr__(self):
52 def __repr__(self):
55 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
53 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
56
54
57
55
58 class RepoTemp(object):
56 class RepoTemp(object):
59 def __init__(self, repo_id):
57 def __init__(self, repo_id):
60 self.repo_id = repo_id
58 self.repo_id = repo_id
61
59
62 def __repr__(self):
60 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
61 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
64
62
65 class CachedRepoList(object):
63 class CachedRepoList(object):
66
64
67 def __init__(self, db_repo_list, repos_path, order_by=None):
65 def __init__(self, db_repo_list, repos_path, order_by=None):
68 self.db_repo_list = db_repo_list
66 self.db_repo_list = db_repo_list
69 self.repos_path = repos_path
67 self.repos_path = repos_path
70 self.order_by = order_by
68 self.order_by = order_by
71 self.reversed = (order_by or '').startswith('-')
69 self.reversed = (order_by or '').startswith('-')
72
70
73 def __len__(self):
71 def __len__(self):
74 return len(self.db_repo_list)
72 return len(self.db_repo_list)
75
73
76 def __repr__(self):
74 def __repr__(self):
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
75 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
78
76
79 def __iter__(self):
77 def __iter__(self):
80 for dbr in self.db_repo_list:
78 for dbr in self.db_repo_list:
81
79
82 scmr = dbr.scm_instance_cached
80 scmr = dbr.scm_instance_cached
83
81
84 # check permission at this level
82 # check permission at this level
85 if not HasRepoPermissionAny('repository.read', 'repository.write',
83 if not HasRepoPermissionAny('repository.read', 'repository.write',
86 'repository.admin')(dbr.repo_name,
84 'repository.admin')(dbr.repo_name,
87 'get repo check'):
85 'get repo check'):
88 continue
86 continue
89
87
90 if scmr is None:
88 if scmr is None:
91 log.error('%s this repository is present in database but it '
89 log.error('%s this repository is present in database but it '
92 'cannot be created as an scm instance',
90 'cannot be created as an scm instance',
93 dbr.repo_name)
91 dbr.repo_name)
94 continue
92 continue
95
93
96 last_change = scmr.last_change
94 last_change = scmr.last_change
97 tip = h.get_changeset_safe(scmr, 'tip')
95 tip = h.get_changeset_safe(scmr, 'tip')
98
96
99 tmp_d = {}
97 tmp_d = {}
100 tmp_d['name'] = dbr.repo_name
98 tmp_d['name'] = dbr.repo_name
101 tmp_d['name_sort'] = tmp_d['name'].lower()
99 tmp_d['name_sort'] = tmp_d['name'].lower()
102 tmp_d['description'] = dbr.description
100 tmp_d['description'] = dbr.description
103 tmp_d['description_sort'] = tmp_d['description']
101 tmp_d['description_sort'] = tmp_d['description']
104 tmp_d['last_change'] = last_change
102 tmp_d['last_change'] = last_change
105 tmp_d['last_change_sort'] = time.mktime(last_change \
103 tmp_d['last_change_sort'] = time.mktime(last_change \
106 .timetuple())
104 .timetuple())
107 tmp_d['tip'] = tip.raw_id
105 tmp_d['tip'] = tip.raw_id
108 tmp_d['tip_sort'] = tip.revision
106 tmp_d['tip_sort'] = tip.revision
109 tmp_d['rev'] = tip.revision
107 tmp_d['rev'] = tip.revision
110 tmp_d['contact'] = dbr.user.full_contact
108 tmp_d['contact'] = dbr.user.full_contact
111 tmp_d['contact_sort'] = tmp_d['contact']
109 tmp_d['contact_sort'] = tmp_d['contact']
112 tmp_d['owner_sort'] = tmp_d['contact']
110 tmp_d['owner_sort'] = tmp_d['contact']
113 tmp_d['repo_archives'] = list(scmr._get_archives())
111 tmp_d['repo_archives'] = list(scmr._get_archives())
114 tmp_d['last_msg'] = tip.message
112 tmp_d['last_msg'] = tip.message
115 tmp_d['author'] = tip.author
113 tmp_d['author'] = tip.author
116 tmp_d['dbrepo'] = dbr.get_dict()
114 tmp_d['dbrepo'] = dbr.get_dict()
117 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
115 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
118 else {}
116 else {}
119 yield tmp_d
117 yield tmp_d
120
118
121 class ScmModel(BaseModel):
119 class ScmModel(BaseModel):
122 """Generic Scm Model
120 """
121 Generic Scm Model
123 """
122 """
124
123
125 @LazyProperty
124 @LazyProperty
126 def repos_path(self):
125 def repos_path(self):
127 """Get's the repositories root path from database
126 """
127 Get's the repositories root path from database
128 """
128 """
129
129
130 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
130 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
131
131
132 return q.ui_value
132 return q.ui_value
133
133
134 def repo_scan(self, repos_path=None):
134 def repo_scan(self, repos_path=None):
135 """Listing of repositories in given path. This path should not be a
135 """
136 Listing of repositories in given path. This path should not be a
136 repository itself. Return a dictionary of repository objects
137 repository itself. Return a dictionary of repository objects
137
138
138 :param repos_path: path to directory containing repositories
139 :param repos_path: path to directory containing repositories
139 """
140 """
140
141
141 log.info('scanning for repositories in %s', repos_path)
142 log.info('scanning for repositories in %s', repos_path)
142
143
143 if repos_path is None:
144 if repos_path is None:
144 repos_path = self.repos_path
145 repos_path = self.repos_path
145
146
146 baseui = make_ui('db')
147 baseui = make_ui('db')
147 repos_list = {}
148 repos_list = {}
148
149
149 for name, path in get_filesystem_repos(repos_path, recursive=True):
150 for name, path in get_filesystem_repos(repos_path, recursive=True):
150
151
151 # name need to be decomposed and put back together using the /
152 # name need to be decomposed and put back together using the /
152 # since this is internal storage separator for rhodecode
153 # since this is internal storage separator for rhodecode
153 name = Repository.url_sep().join(name.split(os.sep))
154 name = Repository.url_sep().join(name.split(os.sep))
154
155
155 try:
156 try:
156 if name in repos_list:
157 if name in repos_list:
157 raise RepositoryError('Duplicate repository name %s '
158 raise RepositoryError('Duplicate repository name %s '
158 'found in %s' % (name, path))
159 'found in %s' % (name, path))
159 else:
160 else:
160
161
161 klass = get_backend(path[0])
162 klass = get_backend(path[0])
162
163
163 if path[0] == 'hg' and path[0] in BACKENDS.keys():
164 if path[0] == 'hg' and path[0] in BACKENDS.keys():
164
165
165 # for mercurial we need to have an str path
166 # for mercurial we need to have an str path
166 repos_list[name] = klass(safe_str(path[1]),
167 repos_list[name] = klass(safe_str(path[1]),
167 baseui=baseui)
168 baseui=baseui)
168
169
169 if path[0] == 'git' and path[0] in BACKENDS.keys():
170 if path[0] == 'git' and path[0] in BACKENDS.keys():
170 repos_list[name] = klass(path[1])
171 repos_list[name] = klass(path[1])
171 except OSError:
172 except OSError:
172 continue
173 continue
173
174
174 return repos_list
175 return repos_list
175
176
176 def get_repos(self, all_repos=None, sort_key=None):
177 def get_repos(self, all_repos=None, sort_key=None):
177 """
178 """
178 Get all repos from db and for each repo create it's
179 Get all repos from db and for each repo create it's
179 backend instance and fill that backed with information from database
180 backend instance and fill that backed with information from database
180
181
181 :param all_repos: list of repository names as strings
182 :param all_repos: list of repository names as strings
182 give specific repositories list, good for filtering
183 give specific repositories list, good for filtering
183 """
184 """
184 if all_repos is None:
185 if all_repos is None:
185 all_repos = self.sa.query(Repository)\
186 all_repos = self.sa.query(Repository)\
186 .filter(Repository.group_id == None)\
187 .filter(Repository.group_id == None)\
187 .order_by(Repository.repo_name).all()
188 .order_by(Repository.repo_name).all()
188
189
189 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
190 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
190 order_by=sort_key)
191 order_by=sort_key)
191
192
192 return repo_iter
193 return repo_iter
193
194
194 def mark_for_invalidation(self, repo_name):
195 def mark_for_invalidation(self, repo_name):
195 """Puts cache invalidation task into db for
196 """Puts cache invalidation task into db for
196 further global cache invalidation
197 further global cache invalidation
197
198
198 :param repo_name: this repo that should invalidation take place
199 :param repo_name: this repo that should invalidation take place
199 """
200 """
200 CacheInvalidation.set_invalidate(repo_name)
201 CacheInvalidation.set_invalidate(repo_name)
201 CacheInvalidation.set_invalidate(repo_name+"_README")
202 CacheInvalidation.set_invalidate(repo_name + "_README")
202
203
203 def toggle_following_repo(self, follow_repo_id, user_id):
204 def toggle_following_repo(self, follow_repo_id, user_id):
204
205
205 f = self.sa.query(UserFollowing)\
206 f = self.sa.query(UserFollowing)\
206 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
207 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
207 .filter(UserFollowing.user_id == user_id).scalar()
208 .filter(UserFollowing.user_id == user_id).scalar()
208
209
209 if f is not None:
210 if f is not None:
210
211
211 try:
212 try:
212 self.sa.delete(f)
213 self.sa.delete(f)
213 self.sa.commit()
214 self.sa.commit()
214 action_logger(UserTemp(user_id),
215 action_logger(UserTemp(user_id),
215 'stopped_following_repo',
216 'stopped_following_repo',
216 RepoTemp(follow_repo_id))
217 RepoTemp(follow_repo_id))
217 return
218 return
218 except:
219 except:
219 log.error(traceback.format_exc())
220 log.error(traceback.format_exc())
220 self.sa.rollback()
221 self.sa.rollback()
221 raise
222 raise
222
223
223 try:
224 try:
224 f = UserFollowing()
225 f = UserFollowing()
225 f.user_id = user_id
226 f.user_id = user_id
226 f.follows_repo_id = follow_repo_id
227 f.follows_repo_id = follow_repo_id
227 self.sa.add(f)
228 self.sa.add(f)
228 self.sa.commit()
229 self.sa.commit()
229 action_logger(UserTemp(user_id),
230 action_logger(UserTemp(user_id),
230 'started_following_repo',
231 'started_following_repo',
231 RepoTemp(follow_repo_id))
232 RepoTemp(follow_repo_id))
232 except:
233 except:
233 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
234 self.sa.rollback()
235 self.sa.rollback()
235 raise
236 raise
236
237
237 def toggle_following_user(self, follow_user_id, user_id):
238 def toggle_following_user(self, follow_user_id, user_id):
238 f = self.sa.query(UserFollowing)\
239 f = self.sa.query(UserFollowing)\
239 .filter(UserFollowing.follows_user_id == follow_user_id)\
240 .filter(UserFollowing.follows_user_id == follow_user_id)\
240 .filter(UserFollowing.user_id == user_id).scalar()
241 .filter(UserFollowing.user_id == user_id).scalar()
241
242
242 if f is not None:
243 if f is not None:
243 try:
244 try:
244 self.sa.delete(f)
245 self.sa.delete(f)
245 self.sa.commit()
246 self.sa.commit()
246 return
247 return
247 except:
248 except:
248 log.error(traceback.format_exc())
249 log.error(traceback.format_exc())
249 self.sa.rollback()
250 self.sa.rollback()
250 raise
251 raise
251
252
252 try:
253 try:
253 f = UserFollowing()
254 f = UserFollowing()
254 f.user_id = user_id
255 f.user_id = user_id
255 f.follows_user_id = follow_user_id
256 f.follows_user_id = follow_user_id
256 self.sa.add(f)
257 self.sa.add(f)
257 self.sa.commit()
258 self.sa.commit()
258 except:
259 except:
259 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
260 self.sa.rollback()
261 self.sa.rollback()
261 raise
262 raise
262
263
263 def is_following_repo(self, repo_name, user_id, cache=False):
264 def is_following_repo(self, repo_name, user_id, cache=False):
264 r = self.sa.query(Repository)\
265 r = self.sa.query(Repository)\
265 .filter(Repository.repo_name == repo_name).scalar()
266 .filter(Repository.repo_name == repo_name).scalar()
266
267
267 f = self.sa.query(UserFollowing)\
268 f = self.sa.query(UserFollowing)\
268 .filter(UserFollowing.follows_repository == r)\
269 .filter(UserFollowing.follows_repository == r)\
269 .filter(UserFollowing.user_id == user_id).scalar()
270 .filter(UserFollowing.user_id == user_id).scalar()
270
271
271 return f is not None
272 return f is not None
272
273
273 def is_following_user(self, username, user_id, cache=False):
274 def is_following_user(self, username, user_id, cache=False):
274 u = User.get_by_username(username)
275 u = User.get_by_username(username)
275
276
276 f = self.sa.query(UserFollowing)\
277 f = self.sa.query(UserFollowing)\
277 .filter(UserFollowing.follows_user == u)\
278 .filter(UserFollowing.follows_user == u)\
278 .filter(UserFollowing.user_id == user_id).scalar()
279 .filter(UserFollowing.user_id == user_id).scalar()
279
280
280 return f is not None
281 return f is not None
281
282
282 def get_followers(self, repo_id):
283 def get_followers(self, repo_id):
283 if not isinstance(repo_id, int):
284 if not isinstance(repo_id, int):
284 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
285 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
285
286
286 return self.sa.query(UserFollowing)\
287 return self.sa.query(UserFollowing)\
287 .filter(UserFollowing.follows_repo_id == repo_id).count()
288 .filter(UserFollowing.follows_repo_id == repo_id).count()
288
289
289 def get_forks(self, repo_id):
290 def get_forks(self, repo_id):
290 if not isinstance(repo_id, int):
291 if not isinstance(repo_id, int):
291 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
292 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
292
293
293 return self.sa.query(Repository)\
294 return self.sa.query(Repository)\
294 .filter(Repository.fork_id == repo_id).count()
295 .filter(Repository.fork_id == repo_id).count()
295
296
296 def pull_changes(self, repo_name, username):
297 def pull_changes(self, repo_name, username):
297 dbrepo = Repository.get_by_repo_name(repo_name)
298 dbrepo = Repository.get_by_repo_name(repo_name)
298 clone_uri = dbrepo.clone_uri
299 clone_uri = dbrepo.clone_uri
299 if not clone_uri:
300 if not clone_uri:
300 raise Exception("This repository doesn't have a clone uri")
301 raise Exception("This repository doesn't have a clone uri")
301
302
302 repo = dbrepo.scm_instance
303 repo = dbrepo.scm_instance
303 try:
304 try:
304 extras = {'ip': '',
305 extras = {'ip': '',
305 'username': username,
306 'username': username,
306 'action': 'push_remote',
307 'action': 'push_remote',
307 'repository': repo_name}
308 'repository': repo_name}
308
309
309 #inject ui extra param to log this action via push logger
310 #inject ui extra param to log this action via push logger
310 for k, v in extras.items():
311 for k, v in extras.items():
311 repo._repo.ui.setconfig('rhodecode_extras', k, v)
312 repo._repo.ui.setconfig('rhodecode_extras', k, v)
312
313
313 repo.pull(clone_uri)
314 repo.pull(clone_uri)
314 self.mark_for_invalidation(repo_name)
315 self.mark_for_invalidation(repo_name)
315 except:
316 except:
316 log.error(traceback.format_exc())
317 log.error(traceback.format_exc())
317 raise
318 raise
318
319
319 def commit_change(self, repo, repo_name, cs, user, author, message, content,
320 def commit_change(self, repo, repo_name, cs, user, author, message, content,
320 f_path):
321 f_path):
321
322
322 if repo.alias == 'hg':
323 if repo.alias == 'hg':
323 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
324 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
324 elif repo.alias == 'git':
325 elif repo.alias == 'git':
325 from vcs.backends.git import GitInMemoryChangeset as IMC
326 from vcs.backends.git import GitInMemoryChangeset as IMC
326
327
327 # decoding here will force that we have proper encoded values
328 # decoding here will force that we have proper encoded values
328 # in any other case this will throw exceptions and deny commit
329 # in any other case this will throw exceptions and deny commit
329 content = safe_str(content)
330 content = safe_str(content)
330 message = safe_str(message)
331 message = safe_str(message)
331 path = safe_str(f_path)
332 path = safe_str(f_path)
332 author = safe_str(author)
333 author = safe_str(author)
333 m = IMC(repo)
334 m = IMC(repo)
334 m.change(FileNode(path, content))
335 m.change(FileNode(path, content))
335 tip = m.commit(message=message,
336 tip = m.commit(message=message,
336 author=author,
337 author=author,
337 parents=[cs], branch=cs.branch)
338 parents=[cs], branch=cs.branch)
338
339
339 new_cs = tip.short_id
340 new_cs = tip.short_id
340 action = 'push_local:%s' % new_cs
341 action = 'push_local:%s' % new_cs
341
342
342 action_logger(user, action, repo_name)
343 action_logger(user, action, repo_name)
343
344
344 self.mark_for_invalidation(repo_name)
345 self.mark_for_invalidation(repo_name)
345
346
346 def create_node(self, repo, repo_name, cs, user, author, message, content,
347 def create_node(self, repo, repo_name, cs, user, author, message, content,
347 f_path):
348 f_path):
348 if repo.alias == 'hg':
349 if repo.alias == 'hg':
349 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
350 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
350 elif repo.alias == 'git':
351 elif repo.alias == 'git':
351 from vcs.backends.git import GitInMemoryChangeset as IMC
352 from vcs.backends.git import GitInMemoryChangeset as IMC
352 # decoding here will force that we have proper encoded values
353 # decoding here will force that we have proper encoded values
353 # in any other case this will throw exceptions and deny commit
354 # in any other case this will throw exceptions and deny commit
354
355
355 if isinstance(content, (basestring,)):
356 if isinstance(content, (basestring,)):
356 content = safe_str(content)
357 content = safe_str(content)
357 elif isinstance(content, file):
358 elif isinstance(content, file):
358 content = content.read()
359 content = content.read()
359
360
360 message = safe_str(message)
361 message = safe_str(message)
361 path = safe_str(f_path)
362 path = safe_str(f_path)
362 author = safe_str(author)
363 author = safe_str(author)
363 m = IMC(repo)
364 m = IMC(repo)
364
365
365 if isinstance(cs, EmptyChangeset):
366 if isinstance(cs, EmptyChangeset):
366 # Emptychangeset means we we're editing empty repository
367 # Emptychangeset means we we're editing empty repository
367 parents = None
368 parents = None
368 else:
369 else:
369 parents = [cs]
370 parents = [cs]
370
371
371 m.add(FileNode(path, content=content))
372 m.add(FileNode(path, content=content))
372 tip = m.commit(message=message,
373 tip = m.commit(message=message,
373 author=author,
374 author=author,
374 parents=parents, branch=cs.branch)
375 parents=parents, branch=cs.branch)
375 new_cs = tip.short_id
376 new_cs = tip.short_id
376 action = 'push_local:%s' % new_cs
377 action = 'push_local:%s' % new_cs
377
378
378 action_logger(user, action, repo_name)
379 action_logger(user, action, repo_name)
379
380
380 self.mark_for_invalidation(repo_name)
381 self.mark_for_invalidation(repo_name)
381
382
382 def get_unread_journal(self):
383 def get_unread_journal(self):
383 return self.sa.query(UserLog).count()
384 return self.sa.query(UserLog).count()
@@ -1,474 +1,475 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import safe_unicode
31 from rhodecode.lib import safe_unicode
32 from rhodecode.lib.caching_query import FromCache
32 from rhodecode.lib.caching_query import FromCache
33
33
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
35 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
36 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
36 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
37 from rhodecode.lib.exceptions import DefaultUserException, \
37 from rhodecode.lib.exceptions import DefaultUserException, \
38 UserOwnsReposException
38 UserOwnsReposException
39
39
40 from sqlalchemy.exc import DatabaseError
40 from sqlalchemy.exc import DatabaseError
41 from rhodecode.lib import generate_api_key
41 from rhodecode.lib import generate_api_key
42 from sqlalchemy.orm import joinedload
42 from sqlalchemy.orm import joinedload
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46 PERM_WEIGHTS = {'repository.none': 0,
46 PERM_WEIGHTS = {'repository.none': 0,
47 'repository.read': 1,
47 'repository.read': 1,
48 'repository.write': 3,
48 'repository.write': 3,
49 'repository.admin': 3}
49 'repository.admin': 3}
50
50
51
51
52 class UserModel(BaseModel):
52 class UserModel(BaseModel):
53
53 def get(self, user_id, cache=False):
54 def get(self, user_id, cache=False):
54 user = self.sa.query(User)
55 user = self.sa.query(User)
55 if cache:
56 if cache:
56 user = user.options(FromCache("sql_cache_short",
57 user = user.options(FromCache("sql_cache_short",
57 "get_user_%s" % user_id))
58 "get_user_%s" % user_id))
58 return user.get(user_id)
59 return user.get(user_id)
59
60
60 def get_by_username(self, username, cache=False, case_insensitive=False):
61 def get_by_username(self, username, cache=False, case_insensitive=False):
61
62
62 if case_insensitive:
63 if case_insensitive:
63 user = self.sa.query(User).filter(User.username.ilike(username))
64 user = self.sa.query(User).filter(User.username.ilike(username))
64 else:
65 else:
65 user = self.sa.query(User)\
66 user = self.sa.query(User)\
66 .filter(User.username == username)
67 .filter(User.username == username)
67 if cache:
68 if cache:
68 user = user.options(FromCache("sql_cache_short",
69 user = user.options(FromCache("sql_cache_short",
69 "get_user_%s" % username))
70 "get_user_%s" % username))
70 return user.scalar()
71 return user.scalar()
71
72
72 def get_by_api_key(self, api_key, cache=False):
73 def get_by_api_key(self, api_key, cache=False):
73 return User.get_by_api_key(api_key, cache)
74 return User.get_by_api_key(api_key, cache)
74
75
75 def create(self, form_data):
76 def create(self, form_data):
76 try:
77 try:
77 new_user = User()
78 new_user = User()
78 for k, v in form_data.items():
79 for k, v in form_data.items():
79 setattr(new_user, k, v)
80 setattr(new_user, k, v)
80
81
81 new_user.api_key = generate_api_key(form_data['username'])
82 new_user.api_key = generate_api_key(form_data['username'])
82 self.sa.add(new_user)
83 self.sa.add(new_user)
83 self.sa.commit()
84 self.sa.commit()
84 return new_user
85 return new_user
85 except:
86 except:
86 log.error(traceback.format_exc())
87 log.error(traceback.format_exc())
87 self.sa.rollback()
88 self.sa.rollback()
88 raise
89 raise
89
90
90
91
91 def create_or_update(self, username, password, email, name, lastname,
92 def create_or_update(self, username, password, email, name, lastname,
92 active=True, admin=False, ldap_dn=None):
93 active=True, admin=False, ldap_dn=None):
93 """
94 """
94 Creates a new instance if not found, or updates current one
95 Creates a new instance if not found, or updates current one
95
96
96 :param username:
97 :param username:
97 :param password:
98 :param password:
98 :param email:
99 :param email:
99 :param active:
100 :param active:
100 :param name:
101 :param name:
101 :param lastname:
102 :param lastname:
102 :param active:
103 :param active:
103 :param admin:
104 :param admin:
104 :param ldap_dn:
105 :param ldap_dn:
105 """
106 """
106
107
107 from rhodecode.lib.auth import get_crypt_password
108 from rhodecode.lib.auth import get_crypt_password
108
109
109 log.debug('Checking for %s account in RhodeCode database', username)
110 log.debug('Checking for %s account in RhodeCode database', username)
110 user = User.get_by_username(username, case_insensitive=True)
111 user = User.get_by_username(username, case_insensitive=True)
111 if user is None:
112 if user is None:
112 log.debug('creating new user %s', username)
113 log.debug('creating new user %s', username)
113 new_user = User()
114 new_user = User()
114 else:
115 else:
115 log.debug('updating user %s', username)
116 log.debug('updating user %s', username)
116 new_user = user
117 new_user = user
117
118
118 try:
119 try:
119 new_user.username = username
120 new_user.username = username
120 new_user.admin = admin
121 new_user.admin = admin
121 new_user.password = get_crypt_password(password)
122 new_user.password = get_crypt_password(password)
122 new_user.api_key = generate_api_key(username)
123 new_user.api_key = generate_api_key(username)
123 new_user.email = email
124 new_user.email = email
124 new_user.active = active
125 new_user.active = active
125 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
126 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
126 new_user.name = name
127 new_user.name = name
127 new_user.lastname = lastname
128 new_user.lastname = lastname
128
129
129 self.sa.add(new_user)
130 self.sa.add(new_user)
130 self.sa.commit()
131 self.sa.commit()
131 return new_user
132 return new_user
132 except (DatabaseError,):
133 except (DatabaseError,):
133 log.error(traceback.format_exc())
134 log.error(traceback.format_exc())
134 self.sa.rollback()
135 self.sa.rollback()
135 raise
136 raise
136
137
137
138
138 def create_for_container_auth(self, username, attrs):
139 def create_for_container_auth(self, username, attrs):
139 """
140 """
140 Creates the given user if it's not already in the database
141 Creates the given user if it's not already in the database
141
142
142 :param username:
143 :param username:
143 :param attrs:
144 :param attrs:
144 """
145 """
145 if self.get_by_username(username, case_insensitive=True) is None:
146 if self.get_by_username(username, case_insensitive=True) is None:
146
147
147 # autogenerate email for container account without one
148 # autogenerate email for container account without one
148 generate_email = lambda usr: '%s@container_auth.account' % usr
149 generate_email = lambda usr: '%s@container_auth.account' % usr
149
150
150 try:
151 try:
151 new_user = User()
152 new_user = User()
152 new_user.username = username
153 new_user.username = username
153 new_user.password = None
154 new_user.password = None
154 new_user.api_key = generate_api_key(username)
155 new_user.api_key = generate_api_key(username)
155 new_user.email = attrs['email']
156 new_user.email = attrs['email']
156 new_user.active = attrs.get('active', True)
157 new_user.active = attrs.get('active', True)
157 new_user.name = attrs['name'] or generate_email(username)
158 new_user.name = attrs['name'] or generate_email(username)
158 new_user.lastname = attrs['lastname']
159 new_user.lastname = attrs['lastname']
159
160
160 self.sa.add(new_user)
161 self.sa.add(new_user)
161 self.sa.commit()
162 self.sa.commit()
162 return new_user
163 return new_user
163 except (DatabaseError,):
164 except (DatabaseError,):
164 log.error(traceback.format_exc())
165 log.error(traceback.format_exc())
165 self.sa.rollback()
166 self.sa.rollback()
166 raise
167 raise
167 log.debug('User %s already exists. Skipping creation of account'
168 log.debug('User %s already exists. Skipping creation of account'
168 ' for container auth.', username)
169 ' for container auth.', username)
169 return None
170 return None
170
171
171 def create_ldap(self, username, password, user_dn, attrs):
172 def create_ldap(self, username, password, user_dn, attrs):
172 """
173 """
173 Checks if user is in database, if not creates this user marked
174 Checks if user is in database, if not creates this user marked
174 as ldap user
175 as ldap user
175
176
176 :param username:
177 :param username:
177 :param password:
178 :param password:
178 :param user_dn:
179 :param user_dn:
179 :param attrs:
180 :param attrs:
180 """
181 """
181 from rhodecode.lib.auth import get_crypt_password
182 from rhodecode.lib.auth import get_crypt_password
182 log.debug('Checking for such ldap account in RhodeCode database')
183 log.debug('Checking for such ldap account in RhodeCode database')
183 if self.get_by_username(username, case_insensitive=True) is None:
184 if self.get_by_username(username, case_insensitive=True) is None:
184
185
185 # autogenerate email for ldap account without one
186 # autogenerate email for ldap account without one
186 generate_email = lambda usr: '%s@ldap.account' % usr
187 generate_email = lambda usr: '%s@ldap.account' % usr
187
188
188 try:
189 try:
189 new_user = User()
190 new_user = User()
190 username = username.lower()
191 username = username.lower()
191 # add ldap account always lowercase
192 # add ldap account always lowercase
192 new_user.username = username
193 new_user.username = username
193 new_user.password = get_crypt_password(password)
194 new_user.password = get_crypt_password(password)
194 new_user.api_key = generate_api_key(username)
195 new_user.api_key = generate_api_key(username)
195 new_user.email = attrs['email'] or generate_email(username)
196 new_user.email = attrs['email'] or generate_email(username)
196 new_user.active = attrs.get('active', True)
197 new_user.active = attrs.get('active', True)
197 new_user.ldap_dn = safe_unicode(user_dn)
198 new_user.ldap_dn = safe_unicode(user_dn)
198 new_user.name = attrs['name']
199 new_user.name = attrs['name']
199 new_user.lastname = attrs['lastname']
200 new_user.lastname = attrs['lastname']
200
201
201 self.sa.add(new_user)
202 self.sa.add(new_user)
202 self.sa.commit()
203 self.sa.commit()
203 return new_user
204 return new_user
204 except (DatabaseError,):
205 except (DatabaseError,):
205 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
206 self.sa.rollback()
207 self.sa.rollback()
207 raise
208 raise
208 log.debug('this %s user exists skipping creation of ldap account',
209 log.debug('this %s user exists skipping creation of ldap account',
209 username)
210 username)
210 return None
211 return None
211
212
212 def create_registration(self, form_data):
213 def create_registration(self, form_data):
213 from rhodecode.lib.celerylib import tasks, run_task
214 from rhodecode.lib.celerylib import tasks, run_task
214 try:
215 try:
215 new_user = User()
216 new_user = User()
216 for k, v in form_data.items():
217 for k, v in form_data.items():
217 if k != 'admin':
218 if k != 'admin':
218 setattr(new_user, k, v)
219 setattr(new_user, k, v)
219
220
220 self.sa.add(new_user)
221 self.sa.add(new_user)
221 self.sa.commit()
222 self.sa.commit()
222 body = ('New user registration\n'
223 body = ('New user registration\n'
223 'username: %s\n'
224 'username: %s\n'
224 'email: %s\n')
225 'email: %s\n')
225 body = body % (form_data['username'], form_data['email'])
226 body = body % (form_data['username'], form_data['email'])
226
227
227 run_task(tasks.send_email, None,
228 run_task(tasks.send_email, None,
228 _('[RhodeCode] New User registration'),
229 _('[RhodeCode] New User registration'),
229 body)
230 body)
230 except:
231 except:
231 log.error(traceback.format_exc())
232 log.error(traceback.format_exc())
232 self.sa.rollback()
233 self.sa.rollback()
233 raise
234 raise
234
235
235 def update(self, user_id, form_data):
236 def update(self, user_id, form_data):
236 try:
237 try:
237 user = self.get(user_id, cache=False)
238 user = self.get(user_id, cache=False)
238 if user.username == 'default':
239 if user.username == 'default':
239 raise DefaultUserException(
240 raise DefaultUserException(
240 _("You can't Edit this user since it's"
241 _("You can't Edit this user since it's"
241 " crucial for entire application"))
242 " crucial for entire application"))
242
243
243 for k, v in form_data.items():
244 for k, v in form_data.items():
244 if k == 'new_password' and v != '':
245 if k == 'new_password' and v != '':
245 user.password = v
246 user.password = v
246 user.api_key = generate_api_key(user.username)
247 user.api_key = generate_api_key(user.username)
247 else:
248 else:
248 setattr(user, k, v)
249 setattr(user, k, v)
249
250
250 self.sa.add(user)
251 self.sa.add(user)
251 self.sa.commit()
252 self.sa.commit()
252 except:
253 except:
253 log.error(traceback.format_exc())
254 log.error(traceback.format_exc())
254 self.sa.rollback()
255 self.sa.rollback()
255 raise
256 raise
256
257
257 def update_my_account(self, user_id, form_data):
258 def update_my_account(self, user_id, form_data):
258 try:
259 try:
259 user = self.get(user_id, cache=False)
260 user = self.get(user_id, cache=False)
260 if user.username == 'default':
261 if user.username == 'default':
261 raise DefaultUserException(
262 raise DefaultUserException(
262 _("You can't Edit this user since it's"
263 _("You can't Edit this user since it's"
263 " crucial for entire application"))
264 " crucial for entire application"))
264 for k, v in form_data.items():
265 for k, v in form_data.items():
265 if k == 'new_password' and v != '':
266 if k == 'new_password' and v != '':
266 user.password = v
267 user.password = v
267 user.api_key = generate_api_key(user.username)
268 user.api_key = generate_api_key(user.username)
268 else:
269 else:
269 if k not in ['admin', 'active']:
270 if k not in ['admin', 'active']:
270 setattr(user, k, v)
271 setattr(user, k, v)
271
272
272 self.sa.add(user)
273 self.sa.add(user)
273 self.sa.commit()
274 self.sa.commit()
274 except:
275 except:
275 log.error(traceback.format_exc())
276 log.error(traceback.format_exc())
276 self.sa.rollback()
277 self.sa.rollback()
277 raise
278 raise
278
279
279 def delete(self, user_id):
280 def delete(self, user_id):
280 try:
281 try:
281 user = self.get(user_id, cache=False)
282 user = self.get(user_id, cache=False)
282 if user.username == 'default':
283 if user.username == 'default':
283 raise DefaultUserException(
284 raise DefaultUserException(
284 _("You can't remove this user since it's"
285 _("You can't remove this user since it's"
285 " crucial for entire application"))
286 " crucial for entire application"))
286 if user.repositories:
287 if user.repositories:
287 raise UserOwnsReposException(_('This user still owns %s '
288 raise UserOwnsReposException(_('This user still owns %s '
288 'repositories and cannot be '
289 'repositories and cannot be '
289 'removed. Switch owners or '
290 'removed. Switch owners or '
290 'remove those repositories') \
291 'remove those repositories') \
291 % user.repositories)
292 % user.repositories)
292 self.sa.delete(user)
293 self.sa.delete(user)
293 self.sa.commit()
294 self.sa.commit()
294 except:
295 except:
295 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
296 self.sa.rollback()
297 self.sa.rollback()
297 raise
298 raise
298
299
299 def reset_password_link(self, data):
300 def reset_password_link(self, data):
300 from rhodecode.lib.celerylib import tasks, run_task
301 from rhodecode.lib.celerylib import tasks, run_task
301 run_task(tasks.send_password_link, data['email'])
302 run_task(tasks.send_password_link, data['email'])
302
303
303 def reset_password(self, data):
304 def reset_password(self, data):
304 from rhodecode.lib.celerylib import tasks, run_task
305 from rhodecode.lib.celerylib import tasks, run_task
305 run_task(tasks.reset_user_password, data['email'])
306 run_task(tasks.reset_user_password, data['email'])
306
307
307 def fill_data(self, auth_user, user_id=None, api_key=None):
308 def fill_data(self, auth_user, user_id=None, api_key=None):
308 """
309 """
309 Fetches auth_user by user_id,or api_key if present.
310 Fetches auth_user by user_id,or api_key if present.
310 Fills auth_user attributes with those taken from database.
311 Fills auth_user attributes with those taken from database.
311 Additionally set's is_authenitated if lookup fails
312 Additionally set's is_authenitated if lookup fails
312 present in database
313 present in database
313
314
314 :param auth_user: instance of user to set attributes
315 :param auth_user: instance of user to set attributes
315 :param user_id: user id to fetch by
316 :param user_id: user id to fetch by
316 :param api_key: api key to fetch by
317 :param api_key: api key to fetch by
317 """
318 """
318 if user_id is None and api_key is None:
319 if user_id is None and api_key is None:
319 raise Exception('You need to pass user_id or api_key')
320 raise Exception('You need to pass user_id or api_key')
320
321
321 try:
322 try:
322 if api_key:
323 if api_key:
323 dbuser = self.get_by_api_key(api_key)
324 dbuser = self.get_by_api_key(api_key)
324 else:
325 else:
325 dbuser = self.get(user_id)
326 dbuser = self.get(user_id)
326
327
327 if dbuser is not None and dbuser.active:
328 if dbuser is not None and dbuser.active:
328 log.debug('filling %s data', dbuser)
329 log.debug('filling %s data', dbuser)
329 for k, v in dbuser.get_dict().items():
330 for k, v in dbuser.get_dict().items():
330 setattr(auth_user, k, v)
331 setattr(auth_user, k, v)
331 else:
332 else:
332 return False
333 return False
333
334
334 except:
335 except:
335 log.error(traceback.format_exc())
336 log.error(traceback.format_exc())
336 auth_user.is_authenticated = False
337 auth_user.is_authenticated = False
337 return False
338 return False
338
339
339 return True
340 return True
340
341
341 def fill_perms(self, user):
342 def fill_perms(self, user):
342 """
343 """
343 Fills user permission attribute with permissions taken from database
344 Fills user permission attribute with permissions taken from database
344 works for permissions given for repositories, and for permissions that
345 works for permissions given for repositories, and for permissions that
345 are granted to groups
346 are granted to groups
346
347
347 :param user: user instance to fill his perms
348 :param user: user instance to fill his perms
348 """
349 """
349
350
350 user.permissions['repositories'] = {}
351 user.permissions['repositories'] = {}
351 user.permissions['global'] = set()
352 user.permissions['global'] = set()
352
353
353 #======================================================================
354 #======================================================================
354 # fetch default permissions
355 # fetch default permissions
355 #======================================================================
356 #======================================================================
356 default_user = User.get_by_username('default')
357 default_user = User.get_by_username('default')
357
358
358 default_perms = self.sa.query(UserRepoToPerm, Repository, Permission)\
359 default_perms = self.sa.query(UserRepoToPerm, Repository, Permission)\
359 .join((Repository, UserRepoToPerm.repository_id ==
360 .join((Repository, UserRepoToPerm.repository_id ==
360 Repository.repo_id))\
361 Repository.repo_id))\
361 .join((Permission, UserRepoToPerm.permission_id ==
362 .join((Permission, UserRepoToPerm.permission_id ==
362 Permission.permission_id))\
363 Permission.permission_id))\
363 .filter(UserRepoToPerm.user == default_user).all()
364 .filter(UserRepoToPerm.user == default_user).all()
364
365
365 if user.is_admin:
366 if user.is_admin:
366 #==================================================================
367 #==================================================================
367 # #admin have all default rights set to admin
368 # #admin have all default rights set to admin
368 #==================================================================
369 #==================================================================
369 user.permissions['global'].add('hg.admin')
370 user.permissions['global'].add('hg.admin')
370
371
371 for perm in default_perms:
372 for perm in default_perms:
372 p = 'repository.admin'
373 p = 'repository.admin'
373 user.permissions['repositories'][perm.UserRepoToPerm.
374 user.permissions['repositories'][perm.UserRepoToPerm.
374 repository.repo_name] = p
375 repository.repo_name] = p
375
376
376 else:
377 else:
377 #==================================================================
378 #==================================================================
378 # set default permissions
379 # set default permissions
379 #==================================================================
380 #==================================================================
380 uid = user.user_id
381 uid = user.user_id
381
382
382 #default global
383 #default global
383 default_global_perms = self.sa.query(UserToPerm)\
384 default_global_perms = self.sa.query(UserToPerm)\
384 .filter(UserToPerm.user == default_user)
385 .filter(UserToPerm.user == default_user)
385
386
386 for perm in default_global_perms:
387 for perm in default_global_perms:
387 user.permissions['global'].add(perm.permission.permission_name)
388 user.permissions['global'].add(perm.permission.permission_name)
388
389
389 #default for repositories
390 #default for repositories
390 for perm in default_perms:
391 for perm in default_perms:
391 if perm.Repository.private and not (perm.Repository.user_id ==
392 if perm.Repository.private and not (perm.Repository.user_id ==
392 uid):
393 uid):
393 #diself.sable defaults for private repos,
394 #diself.sable defaults for private repos,
394 p = 'repository.none'
395 p = 'repository.none'
395 elif perm.Repository.user_id == uid:
396 elif perm.Repository.user_id == uid:
396 #set admin if owner
397 #set admin if owner
397 p = 'repository.admin'
398 p = 'repository.admin'
398 else:
399 else:
399 p = perm.Permission.permission_name
400 p = perm.Permission.permission_name
400
401
401 user.permissions['repositories'][perm.UserRepoToPerm.
402 user.permissions['repositories'][perm.UserRepoToPerm.
402 repository.repo_name] = p
403 repository.repo_name] = p
403
404
404 #==================================================================
405 #==================================================================
405 # overwrite default with user permissions if any
406 # overwrite default with user permissions if any
406 #==================================================================
407 #==================================================================
407
408
408 #user global
409 #user global
409 user_perms = self.sa.query(UserToPerm)\
410 user_perms = self.sa.query(UserToPerm)\
410 .options(joinedload(UserToPerm.permission))\
411 .options(joinedload(UserToPerm.permission))\
411 .filter(UserToPerm.user_id == uid).all()
412 .filter(UserToPerm.user_id == uid).all()
412
413
413 for perm in user_perms:
414 for perm in user_perms:
414 user.permissions['global'].add(perm.permission.
415 user.permissions['global'].add(perm.permission.
415 permission_name)
416 permission_name)
416
417
417 #user repositories
418 #user repositories
418 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
419 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
419 Repository)\
420 Repository)\
420 .join((Repository, UserRepoToPerm.repository_id ==
421 .join((Repository, UserRepoToPerm.repository_id ==
421 Repository.repo_id))\
422 Repository.repo_id))\
422 .join((Permission, UserRepoToPerm.permission_id ==
423 .join((Permission, UserRepoToPerm.permission_id ==
423 Permission.permission_id))\
424 Permission.permission_id))\
424 .filter(UserRepoToPerm.user_id == uid).all()
425 .filter(UserRepoToPerm.user_id == uid).all()
425
426
426 for perm in user_repo_perms:
427 for perm in user_repo_perms:
427 # set admin if owner
428 # set admin if owner
428 if perm.Repository.user_id == uid:
429 if perm.Repository.user_id == uid:
429 p = 'repository.admin'
430 p = 'repository.admin'
430 else:
431 else:
431 p = perm.Permission.permission_name
432 p = perm.Permission.permission_name
432 user.permissions['repositories'][perm.UserRepoToPerm.
433 user.permissions['repositories'][perm.UserRepoToPerm.
433 repository.repo_name] = p
434 repository.repo_name] = p
434
435
435 #==================================================================
436 #==================================================================
436 # check if user is part of groups for this repository and fill in
437 # check if user is part of groups for this repository and fill in
437 # (or replace with higher) permissions
438 # (or replace with higher) permissions
438 #==================================================================
439 #==================================================================
439
440
440 #users group global
441 #users group global
441 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
442 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
442 .options(joinedload(UsersGroupToPerm.permission))\
443 .options(joinedload(UsersGroupToPerm.permission))\
443 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
444 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
444 UsersGroupMember.users_group_id))\
445 UsersGroupMember.users_group_id))\
445 .filter(UsersGroupMember.user_id == uid).all()
446 .filter(UsersGroupMember.user_id == uid).all()
446
447
447 for perm in user_perms_from_users_groups:
448 for perm in user_perms_from_users_groups:
448 user.permissions['global'].add(perm.permission.permission_name)
449 user.permissions['global'].add(perm.permission.permission_name)
449
450
450 #users group repositories
451 #users group repositories
451 user_repo_perms_from_users_groups = self.sa.query(
452 user_repo_perms_from_users_groups = self.sa.query(
452 UsersGroupRepoToPerm,
453 UsersGroupRepoToPerm,
453 Permission, Repository,)\
454 Permission, Repository,)\
454 .join((Repository, UsersGroupRepoToPerm.repository_id ==
455 .join((Repository, UsersGroupRepoToPerm.repository_id ==
455 Repository.repo_id))\
456 Repository.repo_id))\
456 .join((Permission, UsersGroupRepoToPerm.permission_id ==
457 .join((Permission, UsersGroupRepoToPerm.permission_id ==
457 Permission.permission_id))\
458 Permission.permission_id))\
458 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
459 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
459 UsersGroupMember.users_group_id))\
460 UsersGroupMember.users_group_id))\
460 .filter(UsersGroupMember.user_id == uid).all()
461 .filter(UsersGroupMember.user_id == uid).all()
461
462
462 for perm in user_repo_perms_from_users_groups:
463 for perm in user_repo_perms_from_users_groups:
463 p = perm.Permission.permission_name
464 p = perm.Permission.permission_name
464 cur_perm = user.permissions['repositories'][perm.
465 cur_perm = user.permissions['repositories'][perm.
465 UsersGroupRepoToPerm.
466 UsersGroupRepoToPerm.
466 repository.repo_name]
467 repository.repo_name]
467 #overwrite permission only if it's greater than permission
468 #overwrite permission only if it's greater than permission
468 # given from other sources
469 # given from other sources
469 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
470 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
470 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
471 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
471 repository.repo_name] = p
472 repository.repo_name] = p
472
473
473 return user
474 return user
474
475
@@ -1,93 +1,92 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.users_group
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users group model for RhodeCode
6 users group model for RhodeCode
7
7
8 :created_on: Oct 1, 2011
8 :created_on: Oct 1, 2011
9 :author: nvinot
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from rhodecode.lib.caching_query import FromCache
30
31 from rhodecode.model import BaseModel
29 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UsersGroupMember, UsersGroup
30 from rhodecode.model.db import UsersGroupMember, UsersGroup
33
31
34 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
35
33
34
36 class UsersGroupModel(BaseModel):
35 class UsersGroupModel(BaseModel):
37
36
38 def __get_users_group(self, users_group):
37 def __get_users_group(self, users_group):
39 return self.__get_instance(UsersGroup, users_group)
38 return self._get_instance(UsersGroup, users_group)
40
39
41 def get(self, users_group_id, cache = False):
40 def get(self, users_group_id, cache=False):
42 return UsersGroup.get(users_group_id)
41 return UsersGroup.get(users_group_id)
43
42
44 def get_by_name(self, name, cache = False, case_insensitive = False):
43 def get_by_name(self, name, cache=False, case_insensitive=False):
45 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
44 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
46
45
47 def create(self, form_data):
46 def create(self, form_data):
48 try:
47 try:
49 new_users_group = UsersGroup()
48 new_users_group = UsersGroup()
50 for k, v in form_data.items():
49 for k, v in form_data.items():
51 setattr(new_users_group, k, v)
50 setattr(new_users_group, k, v)
52
51
53 self.sa.add(new_users_group)
52 self.sa.add(new_users_group)
54 self.sa.commit()
53 self.sa.commit()
55 return new_users_group
54 return new_users_group
56 except:
55 except:
57 log.error(traceback.format_exc())
56 log.error(traceback.format_exc())
58 self.sa.rollback()
57 self.sa.rollback()
59 raise
58 raise
60
59
61
60
62 def create_(self, name, active=True):
61 def create_(self, name, active=True):
63 new = UsersGroup()
62 new = UsersGroup()
64 new.users_group_name = name
63 new.users_group_name = name
65 new.users_group_active = active
64 new.users_group_active = active
66 self.sa.add(new)
65 self.sa.add(new)
67 return new
66 return new
68
67
69 def delete(self, users_group):
68 def delete(self, users_group):
70 obj = self.__get_users_group(users_group)
69 obj = self.__get_users_group(users_group)
71 self.sa.delete(obj)
70 self.sa.delete(obj)
72
71
73 def add_user_to_group(self, users_group, user):
72 def add_user_to_group(self, users_group, user):
74 for m in users_group.members:
73 for m in users_group.members:
75 u = m.user
74 u = m.user
76 if u.user_id == user.user_id:
75 if u.user_id == user.user_id:
77 return m
76 return m
78
77
79 try:
78 try:
80 users_group_member = UsersGroupMember()
79 users_group_member = UsersGroupMember()
81 users_group_member.user = user
80 users_group_member.user = user
82 users_group_member.users_group = users_group
81 users_group_member.users_group = users_group
83
82
84 users_group.members.append(users_group_member)
83 users_group.members.append(users_group_member)
85 user.group_member.append(users_group_member)
84 user.group_member.append(users_group_member)
86
85
87 self.sa.add(users_group_member)
86 self.sa.add(users_group_member)
88 self.sa.commit()
87 self.sa.commit()
89 return users_group_member
88 return users_group_member
90 except:
89 except:
91 log.error(traceback.format_exc())
90 log.error(traceback.format_exc())
92 self.sa.rollback()
91 self.sa.rollback()
93 raise
92 raise
General Comments 0
You need to be logged in to leave comments. Login now