##// END OF EJS Templates
extended user logs to create/delete/fork repositories for auditing...
marcink -
r537:48be9538 default
parent child Browse files
Show More
@@ -1,234 +1,245
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # repos controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 7, 2010
22 22 admin controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from operator import itemgetter
27 27 from paste.httpexceptions import HTTPInternalServerError
28 28 from pylons import request, response, session, tmpl_context as c, url
29 29 from pylons.controllers.util import abort, redirect
30 30 from pylons.i18n.translation import _
31 31 from pylons_app.lib import helpers as h
32 32 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
33 33 HasPermissionAnyDecorator
34 34 from pylons_app.lib.base import BaseController, render
35 from pylons_app.lib.utils import invalidate_cache
35 from pylons_app.lib.utils import invalidate_cache, action_logger
36 36 from pylons_app.model.db import User
37 37 from pylons_app.model.forms import RepoForm
38 38 from pylons_app.model.hg_model import HgModel
39 39 from pylons_app.model.repo_model import RepoModel
40 40 import formencode
41 41 import logging
42 42 import traceback
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46 class ReposController(BaseController):
47 47 """REST Controller styled on the Atom Publishing Protocol"""
48 48 # To properly map this controller, ensure your config/routing.py
49 49 # file has a resource setup:
50 50 # map.resource('repo', 'repos')
51 51
52 52 @LoginRequired()
53 53 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
54 54 def __before__(self):
55 55 c.admin_user = session.get('admin_user')
56 56 c.admin_username = session.get('admin_username')
57 57 super(ReposController, self).__before__()
58 58
59 59 @HasPermissionAllDecorator('hg.admin')
60 60 def index(self, format='html'):
61 61 """GET /repos: All items in the collection"""
62 62 # url('repos')
63 63 cached_repo_list = HgModel().get_repos()
64 64 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
65 65 return render('admin/repos/repos.html')
66 66
67 67 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
68 68 def create(self):
69 69 """POST /repos: Create a new item"""
70 70 # url('repos')
71 71 repo_model = RepoModel()
72 72 _form = RepoForm()()
73 73 form_result = {}
74 74 try:
75 75 form_result = _form.to_python(dict(request.POST))
76 76 repo_model.create(form_result, c.hg_app_user)
77 77 invalidate_cache('cached_repo_list')
78 78 h.flash(_('created repository %s') % form_result['repo_name'],
79 79 category='success')
80
80
81 if request.POST.get('user_created'):
82 action_logger(self.hg_app_user, 'user_created_repo',
83 form_result['repo_name'], '', self.sa)
84 else:
85 action_logger(self.hg_app_user, 'admin_created_repo',
86 form_result['repo_name'], '', self.sa)
87
81 88 except formencode.Invalid as errors:
82 89 c.new_repo = errors.value['repo_name']
83 90
84 91 if request.POST.get('user_created'):
85 92 r = render('admin/repos/repo_add_create_repository.html')
86 else:
93 else:
87 94 r = render('admin/repos/repo_add.html')
88 95
89 96 return htmlfill.render(
90 97 r,
91 98 defaults=errors.value,
92 99 errors=errors.error_dict or {},
93 100 prefix_error=False,
94 101 encoding="UTF-8")
95 102
96 103 except Exception:
97 104 log.error(traceback.format_exc())
98 105 msg = _('error occured during creation of repository %s') \
99 106 % form_result.get('repo_name')
100 107 h.flash(msg, category='error')
101 108 if request.POST.get('user_created'):
102 109 return redirect(url('hg_home'))
103 110 return redirect(url('repos'))
104 111
105 112 @HasPermissionAllDecorator('hg.admin')
106 113 def new(self, format='html'):
107 114 """GET /repos/new: Form to create a new item"""
108 115 new_repo = request.GET.get('repo', '')
109 116 c.new_repo = h.repo_name_slug(new_repo)
110 117
111 118 return render('admin/repos/repo_add.html')
112 119
113 120 @HasPermissionAllDecorator('hg.admin')
114 121 def update(self, repo_name):
115 122 """PUT /repos/repo_name: Update an existing item"""
116 123 # Forms posted to this method should contain a hidden field:
117 124 # <input type="hidden" name="_method" value="PUT" />
118 125 # Or using helpers:
119 126 # h.form(url('repo', repo_name=ID),
120 127 # method='put')
121 128 # url('repo', repo_name=ID)
122 129 repo_model = RepoModel()
123 130 changed_name = repo_name
124 131 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
125 132
126 133 try:
127 134 form_result = _form.to_python(dict(request.POST))
128 135 repo_model.update(repo_name, form_result)
129 136 invalidate_cache('cached_repo_list')
130 137 h.flash(_('Repository %s updated succesfully' % repo_name),
131 138 category='success')
132 139 changed_name = form_result['repo_name']
133 140 except formencode.Invalid as errors:
134 141 c.repo_info = repo_model.get(repo_name)
135 142 c.users_array = repo_model.get_users_js()
136 143 errors.value.update({'user':c.repo_info.user.username})
137 144 return htmlfill.render(
138 145 render('admin/repos/repo_edit.html'),
139 146 defaults=errors.value,
140 147 errors=errors.error_dict or {},
141 148 prefix_error=False,
142 149 encoding="UTF-8")
143 150
144 151 except Exception:
145 152 log.error(traceback.format_exc())
146 153 h.flash(_('error occured during update of repository %s') \
147 154 % repo_name, category='error')
148 155
149 156 return redirect(url('edit_repo', repo_name=changed_name))
150 157
151 158 @HasPermissionAllDecorator('hg.admin')
152 159 def delete(self, repo_name):
153 160 """DELETE /repos/repo_name: Delete an existing item"""
154 161 # Forms posted to this method should contain a hidden field:
155 162 # <input type="hidden" name="_method" value="DELETE" />
156 163 # Or using helpers:
157 164 # h.form(url('repo', repo_name=ID),
158 165 # method='delete')
159 166 # url('repo', repo_name=ID)
160 167
161 168 repo_model = RepoModel()
162 169 repo = repo_model.get(repo_name)
163 170 if not repo:
164 171 h.flash(_('%s repository is not mapped to db perhaps'
165 172 ' it was moved or renamed from the filesystem'
166 173 ' please run the application again'
167 174 ' in order to rescan repositories') % repo_name,
168 175 category='error')
169 176
170 177 return redirect(url('repos'))
171 178 try:
179 action_logger(self.hg_app_user, 'admin_deleted_repo',
180 repo_name, '', self.sa)
172 181 repo_model.delete(repo)
173 182 invalidate_cache('cached_repo_list')
174 183 h.flash(_('deleted repository %s') % repo_name, category='success')
175 except Exception:
184
185 except Exception, e:
186 log.error(traceback.format_exc())
176 187 h.flash(_('An error occured during deletion of %s') % repo_name,
177 188 category='error')
178 189
179 190 return redirect(url('repos'))
180 191
181 192 @HasPermissionAllDecorator('hg.admin')
182 193 def delete_perm_user(self, repo_name):
183 194 """
184 195 DELETE an existing repository permission user
185 196 @param repo_name:
186 197 """
187 198
188 199 try:
189 200 repo_model = RepoModel()
190 201 repo_model.delete_perm_user(request.POST, repo_name)
191 202 except Exception as e:
192 203 h.flash(_('An error occured during deletion of repository user'),
193 204 category='error')
194 205 raise HTTPInternalServerError()
195 206
196 207 @HasPermissionAllDecorator('hg.admin')
197 208 def show(self, repo_name, format='html'):
198 209 """GET /repos/repo_name: Show a specific item"""
199 210 # url('repo', repo_name=ID)
200 211
201 212 @HasPermissionAllDecorator('hg.admin')
202 213 def edit(self, repo_name, format='html'):
203 214 """GET /repos/repo_name/edit: Form to edit an existing item"""
204 215 # url('edit_repo', repo_name=ID)
205 216 repo_model = RepoModel()
206 217 c.repo_info = repo = repo_model.get(repo_name)
207 218 if not repo:
208 219 h.flash(_('%s repository is not mapped to db perhaps'
209 220 ' it was created or renamed from the filesystem'
210 221 ' please run the application again'
211 222 ' in order to rescan repositories') % repo_name,
212 223 category='error')
213 224
214 225 return redirect(url('repos'))
215 226 defaults = c.repo_info.__dict__
216 227 if c.repo_info.user:
217 228 defaults.update({'user':c.repo_info.user.username})
218 229 else:
219 230 replacement_user = self.sa.query(User)\
220 231 .filter(User.admin == True).first().username
221 232 defaults.update({'user':replacement_user})
222 233
223 234 c.users_array = repo_model.get_users_js()
224 235
225 236 for p in c.repo_info.repo_to_perm:
226 237 defaults.update({'perm_%s' % p.user.username:
227 238 p.permission.permission_name})
228 239
229 240 return htmlfill.render(
230 241 render('admin/repos/repo_edit.html'),
231 242 defaults=defaults,
232 243 encoding="UTF-8",
233 244 force_defaults=False
234 245 )
@@ -1,298 +1,298
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on July 14, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 27 config
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 32 HasPermissionAnyDecorator
33 33 from pylons_app.lib.base import BaseController, render
34 34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
35 35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
36 36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
37 37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
38 38 ApplicationUiSettingsForm
39 39 from pylons_app.model.hg_model import HgModel
40 40 from pylons_app.model.user_model import UserModel
41 41 from pylons_app.lib.celerylib import tasks, run_task
42 42 import formencode
43 43 import logging
44 44 import traceback
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class SettingsController(BaseController):
50 50 """REST Controller styled on the Atom Publishing Protocol"""
51 51 # To properly map this controller, ensure your config/routing.py
52 52 # file has a resource setup:
53 53 # map.resource('setting', 'settings', controller='admin/settings',
54 54 # path_prefix='/admin', name_prefix='admin_')
55 55
56 56
57 57 @LoginRequired()
58 58 def __before__(self):
59 59 c.admin_user = session.get('admin_user')
60 60 c.admin_username = session.get('admin_username')
61 61 super(SettingsController, self).__before__()
62 62
63 63
64 64 @HasPermissionAllDecorator('hg.admin')
65 65 def index(self, format='html'):
66 66 """GET /admin/settings: All items in the collection"""
67 67 # url('admin_settings')
68 68
69 69 defaults = get_hg_settings()
70 70 defaults.update(get_hg_ui_settings())
71 71 return htmlfill.render(
72 72 render('admin/settings/settings.html'),
73 73 defaults=defaults,
74 74 encoding="UTF-8",
75 75 force_defaults=False
76 76 )
77 77
78 78 @HasPermissionAllDecorator('hg.admin')
79 79 def create(self):
80 80 """POST /admin/settings: Create a new item"""
81 81 # url('admin_settings')
82 82
83 83 @HasPermissionAllDecorator('hg.admin')
84 84 def new(self, format='html'):
85 85 """GET /admin/settings/new: Form to create a new item"""
86 86 # url('admin_new_setting')
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 def update(self, setting_id):
90 90 """PUT /admin/settings/setting_id: Update an existing item"""
91 91 # Forms posted to this method should contain a hidden field:
92 92 # <input type="hidden" name="_method" value="PUT" />
93 93 # Or using helpers:
94 94 # h.form(url('admin_setting', setting_id=ID),
95 95 # method='put')
96 96 # url('admin_setting', setting_id=ID)
97 97 if setting_id == 'mapping':
98 98 rm_obsolete = request.POST.get('destroy', False)
99 99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
100 100
101 101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
102 102 repo2db_mapper(initial, rm_obsolete)
103 103 invalidate_cache('cached_repo_list')
104 h.flash(_('Repositories sucessfully rescanned'), category='success')
104 h.flash(_('Repositories successfully rescanned'), category='success')
105 105
106 106 if setting_id == 'whoosh':
107 107 repo_location = get_hg_ui_settings()['paths_root_path']
108 108 full_index = request.POST.get('full_index', False)
109 109 task = run_task(tasks.whoosh_index, repo_location, full_index)
110 110
111 111 h.flash(_('Whoosh reindex task scheduled'), category='success')
112 112 if setting_id == 'global':
113 113
114 114 application_form = ApplicationSettingsForm()()
115 115 try:
116 116 form_result = application_form.to_python(dict(request.POST))
117 117
118 118 try:
119 119 hgsettings1 = self.sa.query(HgAppSettings)\
120 120 .filter(HgAppSettings.app_settings_name == 'title').one()
121 121 hgsettings1.app_settings_value = form_result['hg_app_title']
122 122
123 123 hgsettings2 = self.sa.query(HgAppSettings)\
124 124 .filter(HgAppSettings.app_settings_name == 'realm').one()
125 125 hgsettings2.app_settings_value = form_result['hg_app_realm']
126 126
127 127
128 128 self.sa.add(hgsettings1)
129 129 self.sa.add(hgsettings2)
130 130 self.sa.commit()
131 131 set_hg_app_config(config)
132 132 h.flash(_('Updated application settings'),
133 133 category='success')
134 134
135 135 except:
136 136 log.error(traceback.format_exc())
137 h.flash(_('error occured during updating application settings'),
137 h.flash(_('error occurred during updating application settings'),
138 138 category='error')
139 139
140 140 self.sa.rollback()
141 141
142 142
143 143 except formencode.Invalid as errors:
144 144 return htmlfill.render(
145 145 render('admin/settings/settings.html'),
146 146 defaults=errors.value,
147 147 errors=errors.error_dict or {},
148 148 prefix_error=False,
149 149 encoding="UTF-8")
150 150
151 151 if setting_id == 'mercurial':
152 152 application_form = ApplicationUiSettingsForm()()
153 153 try:
154 154 form_result = application_form.to_python(dict(request.POST))
155 155
156 156 try:
157 157
158 158 hgsettings1 = self.sa.query(HgAppUi)\
159 159 .filter(HgAppUi.ui_key == 'push_ssl').one()
160 160 hgsettings1.ui_value = form_result['web_push_ssl']
161 161
162 162 hgsettings2 = self.sa.query(HgAppUi)\
163 163 .filter(HgAppUi.ui_key == '/').one()
164 164 hgsettings2.ui_value = form_result['paths_root_path']
165 165
166 166
167 167 #HOOKS
168 168 hgsettings3 = self.sa.query(HgAppUi)\
169 169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
170 170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
171 171
172 172 hgsettings4 = self.sa.query(HgAppUi)\
173 173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
174 174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
175 175
176 176
177 177
178 178
179 179 self.sa.add(hgsettings1)
180 180 self.sa.add(hgsettings2)
181 181 self.sa.add(hgsettings3)
182 182 self.sa.add(hgsettings4)
183 183 self.sa.commit()
184 184
185 185 h.flash(_('Updated mercurial settings'),
186 186 category='success')
187 187
188 188 except:
189 189 log.error(traceback.format_exc())
190 h.flash(_('error occured during updating application settings'),
190 h.flash(_('error occurred during updating application settings'),
191 191 category='error')
192 192
193 193 self.sa.rollback()
194 194
195 195
196 196 except formencode.Invalid as errors:
197 197 return htmlfill.render(
198 198 render('admin/settings/settings.html'),
199 199 defaults=errors.value,
200 200 errors=errors.error_dict or {},
201 201 prefix_error=False,
202 202 encoding="UTF-8")
203 203
204 204
205 205
206 206 return redirect(url('admin_settings'))
207 207
208 208 @HasPermissionAllDecorator('hg.admin')
209 209 def delete(self, setting_id):
210 210 """DELETE /admin/settings/setting_id: Delete an existing item"""
211 211 # Forms posted to this method should contain a hidden field:
212 212 # <input type="hidden" name="_method" value="DELETE" />
213 213 # Or using helpers:
214 214 # h.form(url('admin_setting', setting_id=ID),
215 215 # method='delete')
216 216 # url('admin_setting', setting_id=ID)
217 217
218 218 @HasPermissionAllDecorator('hg.admin')
219 219 def show(self, setting_id, format='html'):
220 220 """GET /admin/settings/setting_id: Show a specific item"""
221 221 # url('admin_setting', setting_id=ID)
222 222
223 223 @HasPermissionAllDecorator('hg.admin')
224 224 def edit(self, setting_id, format='html'):
225 225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
226 226 # url('admin_edit_setting', setting_id=ID)
227 227
228 228
229 229 def my_account(self):
230 230 """
231 231 GET /_admin/my_account Displays info about my account
232 232 """
233 233 # url('admin_settings_my_account')
234 234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
235 235 c.user_repos = []
236 236 for repo in c.cached_repo_list.values():
237 237 if repo.dbrepo.user.username == c.user.username:
238 238 c.user_repos.append(repo)
239 239
240 240 if c.user.username == 'default':
241 241 h.flash(_("You can't edit this user since it's"
242 242 " crucial for entire application"), category='warning')
243 243 return redirect(url('users'))
244 244
245 245 defaults = c.user.__dict__
246 246 return htmlfill.render(
247 247 render('admin/users/user_edit_my_account.html'),
248 248 defaults=defaults,
249 249 encoding="UTF-8",
250 250 force_defaults=False
251 251 )
252 252
253 253 def my_account_update(self):
254 254 """PUT /_admin/my_account_update: Update an existing item"""
255 255 # Forms posted to this method should contain a hidden field:
256 256 # <input type="hidden" name="_method" value="PUT" />
257 257 # Or using helpers:
258 258 # h.form(url('admin_settings_my_account_update'),
259 259 # method='put')
260 260 # url('admin_settings_my_account_update', id=ID)
261 261 user_model = UserModel()
262 262 uid = c.hg_app_user.user_id
263 263 _form = UserForm(edit=True, old_data={'user_id':uid,
264 264 'email':c.hg_app_user.email})()
265 265 form_result = {}
266 266 try:
267 267 form_result = _form.to_python(dict(request.POST))
268 268 user_model.update_my_account(uid, form_result)
269 269 h.flash(_('Your account was updated succesfully'),
270 270 category='success')
271 271
272 272 except formencode.Invalid as errors:
273 273 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
274 274 c.user_repos = []
275 275 for repo in c.cached_repo_list.values():
276 276 if repo.dbrepo.user.username == c.user.username:
277 277 c.user_repos.append(repo)
278 278 return htmlfill.render(
279 279 render('admin/users/user_edit_my_account.html'),
280 280 defaults=errors.value,
281 281 errors=errors.error_dict or {},
282 282 prefix_error=False,
283 283 encoding="UTF-8")
284 284 except Exception:
285 285 log.error(traceback.format_exc())
286 286 h.flash(_('error occured during update of user %s') \
287 287 % form_result.get('username'), category='error')
288 288
289 289 return redirect(url('my_account'))
290 290
291 291 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
292 292 def create_repository(self):
293 293 """GET /_admin/create_repository: Form to create a new item"""
294 294 new_repo = request.GET.get('repo', '')
295 295 c.new_repo = h.repo_name_slug(new_repo)
296 296
297 297 return render('admin/repos/repo_add_create_repository.html')
298 298
@@ -1,166 +1,168
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # users controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 from pylons_app.lib.utils import action_logger
20 21 """
21 22 Created on April 4, 2010
22 23 users controller for pylons
23 24 @author: marcink
24 25 """
25 26
26 27 from formencode import htmlfill
27 28 from pylons import request, session, tmpl_context as c, url
28 29 from pylons.controllers.util import abort, redirect
29 30 from pylons.i18n.translation import _
30 31 from pylons_app.lib import helpers as h
31 32 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
32 33 from pylons_app.lib.base import BaseController, render
33 34 from pylons_app.model.db import User, UserLog
34 35 from pylons_app.model.forms import UserForm
35 36 from pylons_app.model.user_model import UserModel, DefaultUserException
36 37 import formencode
37 38 import logging
38 39 import traceback
39 40
40 41 log = logging.getLogger(__name__)
41 42
42 43 class UsersController(BaseController):
43 44 """REST Controller styled on the Atom Publishing Protocol"""
44 45 # To properly map this controller, ensure your config/routing.py
45 46 # file has a resource setup:
46 47 # map.resource('user', 'users')
47 48
48 49 @LoginRequired()
49 50 @HasPermissionAllDecorator('hg.admin')
50 51 def __before__(self):
51 52 c.admin_user = session.get('admin_user')
52 53 c.admin_username = session.get('admin_username')
53 54 super(UsersController, self).__before__()
54 55
55 56
56 57 def index(self, format='html'):
57 58 """GET /users: All items in the collection"""
58 59 # url('users')
59 60
60 61 c.users_list = self.sa.query(User).all()
61 62 return render('admin/users/users.html')
62 63
63 64 def create(self):
64 65 """POST /users: Create a new item"""
65 66 # url('users')
66 67
67 68 user_model = UserModel()
68 69 login_form = UserForm()()
69 70 try:
70 71 form_result = login_form.to_python(dict(request.POST))
71 72 user_model.create(form_result)
72 73 h.flash(_('created user %s') % form_result['username'],
73 74 category='success')
75 #action_logger(self.hg_app_user, 'new_user', '', '', self.sa)
74 76 except formencode.Invalid as errors:
75 77 return htmlfill.render(
76 78 render('admin/users/user_add.html'),
77 79 defaults=errors.value,
78 80 errors=errors.error_dict or {},
79 81 prefix_error=False,
80 82 encoding="UTF-8")
81 83 except Exception:
82 84 log.error(traceback.format_exc())
83 85 h.flash(_('error occured during creation of user %s') \
84 86 % request.POST.get('username'), category='error')
85 87 return redirect(url('users'))
86 88
87 89 def new(self, format='html'):
88 90 """GET /users/new: Form to create a new item"""
89 91 # url('new_user')
90 92 return render('admin/users/user_add.html')
91 93
92 94 def update(self, id):
93 95 """PUT /users/id: Update an existing item"""
94 96 # Forms posted to this method should contain a hidden field:
95 97 # <input type="hidden" name="_method" value="PUT" />
96 98 # Or using helpers:
97 99 # h.form(url('user', id=ID),
98 100 # method='put')
99 101 # url('user', id=ID)
100 102 user_model = UserModel()
101 103 c.user = user_model.get_user(id)
102 104
103 105 _form = UserForm(edit=True, old_data={'user_id':id,
104 106 'email':c.user.email})()
105 107 form_result = {}
106 108 try:
107 109 form_result = _form.to_python(dict(request.POST))
108 110 user_model.update(id, form_result)
109 111 h.flash(_('User updated succesfully'), category='success')
110 112
111 113 except formencode.Invalid as errors:
112 114 return htmlfill.render(
113 115 render('admin/users/user_edit.html'),
114 116 defaults=errors.value,
115 117 errors=errors.error_dict or {},
116 118 prefix_error=False,
117 119 encoding="UTF-8")
118 120 except Exception:
119 121 log.error(traceback.format_exc())
120 122 h.flash(_('error occured during update of user %s') \
121 123 % form_result.get('username'), category='error')
122 124
123 125 return redirect(url('users'))
124 126
125 127 def delete(self, id):
126 128 """DELETE /users/id: Delete an existing item"""
127 129 # Forms posted to this method should contain a hidden field:
128 130 # <input type="hidden" name="_method" value="DELETE" />
129 131 # Or using helpers:
130 132 # h.form(url('user', id=ID),
131 133 # method='delete')
132 134 # url('user', id=ID)
133 135 user_model = UserModel()
134 136 try:
135 137 user_model.delete(id)
136 138 h.flash(_('sucessfully deleted user'), category='success')
137 139 except DefaultUserException as e:
138 140 h.flash(str(e), category='warning')
139 141 except Exception:
140 142 h.flash(_('An error occured during deletion of user'),
141 143 category='error')
142 144 return redirect(url('users'))
143 145
144 146 def show(self, id, format='html'):
145 147 """GET /users/id: Show a specific item"""
146 148 # url('user', id=ID)
147 149
148 150
149 151 def edit(self, id, format='html'):
150 152 """GET /users/id/edit: Form to edit an existing item"""
151 153 # url('edit_user', id=ID)
152 154 c.user = self.sa.query(User).get(id)
153 155 if not c.user:
154 156 return redirect(url('users'))
155 157 if c.user.username == 'default':
156 158 h.flash(_("You can't edit this user since it's"
157 159 " crucial for entire application"), category='warning')
158 160 return redirect(url('users'))
159 161
160 162 defaults = c.user.__dict__
161 163 return htmlfill.render(
162 164 render('admin/users/user_edit.html'),
163 165 defaults=defaults,
164 166 encoding="UTF-8",
165 167 force_defaults=False
166 168 )
@@ -1,172 +1,175
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on June 30, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import tmpl_context as c, request, url
27 27 from pylons.controllers.util import redirect
28 28 from pylons.i18n.translation import _
29 29 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
30 30 from pylons_app.lib.base import BaseController, render
31 from pylons_app.lib.utils import invalidate_cache
31 from pylons_app.lib.utils import invalidate_cache, action_logger
32 32 from pylons_app.model.forms import RepoSettingsForm, RepoForkForm
33 33 from pylons_app.model.repo_model import RepoModel
34 34 import formencode
35 35 import logging
36 36 import pylons_app.lib.helpers as h
37 37 import traceback
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41 class SettingsController(BaseController):
42 42
43 43 @LoginRequired()
44 44 @HasRepoPermissionAllDecorator('repository.admin')
45 45 def __before__(self):
46 46 super(SettingsController, self).__before__()
47 47
48 48 def index(self, repo_name):
49 49 repo_model = RepoModel()
50 50 c.repo_info = repo = repo_model.get(repo_name)
51 51 if not repo:
52 52 h.flash(_('%s repository is not mapped to db perhaps'
53 53 ' it was created or renamed from the filesystem'
54 54 ' please run the application again'
55 55 ' in order to rescan repositories') % repo_name,
56 56 category='error')
57 57
58 58 return redirect(url('hg_home'))
59 59 defaults = c.repo_info.__dict__
60 60 defaults.update({'user':c.repo_info.user.username})
61 61 c.users_array = repo_model.get_users_js()
62 62
63 63 for p in c.repo_info.repo_to_perm:
64 64 defaults.update({'perm_%s' % p.user.username:
65 65 p.permission.permission_name})
66 66
67 67 return htmlfill.render(
68 68 render('settings/repo_settings.html'),
69 69 defaults=defaults,
70 70 encoding="UTF-8",
71 71 force_defaults=False
72 72 )
73 73
74 74 def update(self, repo_name):
75 75 repo_model = RepoModel()
76 76 changed_name = repo_name
77 77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
78 78 try:
79 79 form_result = _form.to_python(dict(request.POST))
80 80 repo_model.update(repo_name, form_result)
81 81 invalidate_cache('cached_repo_list')
82 h.flash(_('Repository %s updated succesfully' % repo_name),
82 h.flash(_('Repository %s updated successfully' % repo_name),
83 83 category='success')
84 84 changed_name = form_result['repo_name']
85 85 except formencode.Invalid as errors:
86 86 c.repo_info = repo_model.get(repo_name)
87 87 c.users_array = repo_model.get_users_js()
88 88 errors.value.update({'user':c.repo_info.user.username})
89 89 return htmlfill.render(
90 90 render('settings/repo_settings.html'),
91 91 defaults=errors.value,
92 92 errors=errors.error_dict or {},
93 93 prefix_error=False,
94 94 encoding="UTF-8")
95 95 except Exception:
96 96 log.error(traceback.format_exc())
97 97 h.flash(_('error occured during update of repository %s') \
98 98 % repo_name, category='error')
99 99
100 100 return redirect(url('repo_settings_home', repo_name=changed_name))
101 101
102 102
103 103
104 104 def delete(self, repo_name):
105 105 """DELETE /repos/repo_name: Delete an existing item"""
106 106 # Forms posted to this method should contain a hidden field:
107 107 # <input type="hidden" name="_method" value="DELETE" />
108 108 # Or using helpers:
109 109 # h.form(url('repo_settings_delete', repo_name=ID),
110 110 # method='delete')
111 111 # url('repo_settings_delete', repo_name=ID)
112 112
113 113 repo_model = RepoModel()
114 114 repo = repo_model.get(repo_name)
115 115 if not repo:
116 116 h.flash(_('%s repository is not mapped to db perhaps'
117 117 ' it was moved or renamed from the filesystem'
118 118 ' please run the application again'
119 119 ' in order to rescan repositories') % repo_name,
120 120 category='error')
121 121
122 122 return redirect(url('hg_home'))
123 123 try:
124 action_logger(self.hg_app_user, 'user_deleted_repo',
125 repo_name, '', self.sa)
124 126 repo_model.delete(repo)
125 127 invalidate_cache('cached_repo_list')
126 128 h.flash(_('deleted repository %s') % repo_name, category='success')
127 129 except Exception:
128 h.flash(_('An error occured during deletion of %s') % repo_name,
130 h.flash(_('An error occurred during deletion of %s') % repo_name,
129 131 category='error')
130 132
131 133 return redirect(url('hg_home'))
132 134
133 135 def fork(self, repo_name):
134 136 repo_model = RepoModel()
135 137 c.repo_info = repo = repo_model.get(repo_name)
136 138 if not repo:
137 139 h.flash(_('%s repository is not mapped to db perhaps'
138 140 ' it was created or renamed from the filesystem'
139 141 ' please run the application again'
140 142 ' in order to rescan repositories') % repo_name,
141 143 category='error')
142 144
143 145 return redirect(url('hg_home'))
144 146
145 147 return render('settings/repo_fork.html')
146 148
147 149
148 150
149 151 def fork_create(self, repo_name):
150 152 repo_model = RepoModel()
151 153 c.repo_info = repo_model.get(repo_name)
152 154 _form = RepoForkForm()()
153 155 form_result = {}
154 156 try:
155 157 form_result = _form.to_python(dict(request.POST))
156 158 form_result.update({'repo_name':repo_name})
157 159 repo_model.create_fork(form_result, c.hg_app_user)
158 160 h.flash(_('fork %s repository as %s task added') \
159 161 % (repo_name, form_result['fork_name']),
160 162 category='success')
161
163 action_logger(self.hg_app_user, 'user_forked_repo',
164 repo_name, '', self.sa)
162 165 except formencode.Invalid as errors:
163 166 c.new_repo = errors.value['fork_name']
164 167 r = render('settings/repo_fork.html')
165 168
166 169 return htmlfill.render(
167 170 r,
168 171 defaults=errors.value,
169 172 errors=errors.error_dict or {},
170 173 prefix_error=False,
171 174 encoding="UTF-8")
172 175 return redirect(url('hg_home'))
@@ -1,46 +1,46
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 from pylons import config, tmpl_context as c, request, session
6 6 from pylons.controllers import WSGIController
7 7 from pylons.templating import render_mako as render
8 8 from pylons_app import __version__
9 9 from pylons_app.lib import auth
10 10 from pylons_app.lib.utils import get_repo_slug
11 11 from pylons_app.model import meta
12 12 from pylons_app.model.hg_model import _get_repos_cached, \
13 13 _get_repos_switcher_cached
14 14
15 15 class BaseController(WSGIController):
16 16
17 17 def __before__(self):
18 18 c.hg_app_version = __version__
19 19 c.hg_app_name = config['hg_app_title']
20 20 c.repo_name = get_repo_slug(request)
21 21 c.cached_repo_list = _get_repos_cached()
22 22 c.repo_switcher_list = _get_repos_switcher_cached(c.cached_repo_list)
23 23
24 24 if c.repo_name:
25 25 cached_repo = c.cached_repo_list.get(c.repo_name)
26 26
27 27 if cached_repo:
28 28 c.repository_tags = cached_repo.tags
29 29 c.repository_branches = cached_repo.branches
30 30 else:
31 31 c.repository_tags = {}
32 32 c.repository_branches = {}
33 33
34 34 self.sa = meta.Session
35 35
36 36 def __call__(self, environ, start_response):
37 37 """Invoke the Controller"""
38 38 # WSGIController.__call__ dispatches to the Controller method
39 39 # the request is routed to. This routing information is
40 40 # available in environ['pylons.routes_dict']
41 41 try:
42 42 #putting this here makes sure that we update permissions every time
43 c.hg_app_user = auth.get_user(session)
43 self.hg_app_user = c.hg_app_user = auth.get_user(session)
44 44 return WSGIController.__call__(self, environ, start_response)
45 45 finally:
46 46 meta.Session.remove()
@@ -1,476 +1,476
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 22 Utilities for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from pylons_app.model import meta
29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings,UserLog
29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings, UserLog
30 30 from vcs.backends.base import BaseChangeset
31 31 from vcs.utils.lazy import LazyProperty
32 32 import logging
33 33 import datetime
34 34 import os
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 def get_repo_slug(request):
40 40 return request.environ['pylons.routes_dict'].get('repo_name')
41 41
42 42 def is_mercurial(environ):
43 43 """
44 44 Returns True if request's target is mercurial server - header
45 45 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
46 46 """
47 47 http_accept = environ.get('HTTP_ACCEPT')
48 48 if http_accept and http_accept.startswith('application/mercurial'):
49 49 return True
50 50 return False
51 51
52 52 def action_logger(user, action, repo, ipaddr, sa=None):
53 53 """
54 54 Action logger for various action made by users
55 55 """
56 56
57 57 if not sa:
58 58 sa = meta.Session
59 59
60
61 if hasattr(user, 'user_id'):
62 user_id = user.user_id
63 elif isinstance(user, basestring):
64
65 user_id = sa.Query(User).filter(User.username == user).one()
66 else:
67 raise Exception('You have to provide user object or username')
68
69 60 try:
61 if hasattr(user, 'user_id'):
62 user_id = user.user_id
63 elif isinstance(user, basestring):
64 user_id = sa.query(User).filter(User.username == user).one()
65 else:
66 raise Exception('You have to provide user object or username')
67
68 repo_name = repo.lstrip('/')
70 69 user_log = UserLog()
71 70 user_log.user_id = user_id
72 71 user_log.action = action
72 user_log.repository_name = repo_name
73 73 user_log.repository = sa.query(Repository)\
74 .filter(Repository.repo_name==repo.lstrip('/')).one()
74 .filter(Repository.repo_name == repo_name).one()
75 75 user_log.action_date = datetime.datetime.now()
76 76 user_log.user_ip = ipaddr
77 77 sa.add(user_log)
78 78 sa.commit()
79 79 log.info('Adding user %s, action %s on %s',
80 80 user.username, action, repo)
81 81 except Exception, e:
82 82 raise
83 83 sa.rollback()
84 84 log.error('could not log user action:%s', str(e))
85 85
86 86 def check_repo_dir(paths):
87 87 repos_path = paths[0][1].split('/')
88 88 if repos_path[-1] in ['*', '**']:
89 89 repos_path = repos_path[:-1]
90 90 if repos_path[0] != '/':
91 91 repos_path[0] = '/'
92 92 if not os.path.isdir(os.path.join(*repos_path)):
93 93 raise Exception('Not a valid repository in %s' % paths[0][1])
94 94
95 95 def check_repo_fast(repo_name, base_path):
96 96 if os.path.isdir(os.path.join(base_path, repo_name)):return False
97 97 return True
98 98
99 99 def check_repo(repo_name, base_path, verify=True):
100 100
101 101 repo_path = os.path.join(base_path, repo_name)
102 102
103 103 try:
104 104 if not check_repo_fast(repo_name, base_path):
105 105 return False
106 106 r = hg.repository(ui.ui(), repo_path)
107 107 if verify:
108 108 hg.verify(r)
109 109 #here we hnow that repo exists it was verified
110 110 log.info('%s repo is already created', repo_name)
111 111 return False
112 112 except RepoError:
113 113 #it means that there is no valid repo there...
114 114 log.info('%s repo is free for creation', repo_name)
115 115 return True
116 116
117 117 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
118 118 while True:
119 119 ok = raw_input(prompt)
120 120 if ok in ('y', 'ye', 'yes'): return True
121 121 if ok in ('n', 'no', 'nop', 'nope'): return False
122 122 retries = retries - 1
123 123 if retries < 0: raise IOError
124 124 print complaint
125 125
126 126 @cache_region('super_short_term', 'cached_hg_ui')
127 127 def get_hg_ui_cached():
128 128 try:
129 129 sa = meta.Session
130 130 ret = sa.query(HgAppUi).all()
131 131 finally:
132 132 meta.Session.remove()
133 133 return ret
134 134
135 135
136 136 def get_hg_settings():
137 137 try:
138 138 sa = meta.Session
139 139 ret = sa.query(HgAppSettings).all()
140 140 finally:
141 141 meta.Session.remove()
142 142
143 143 if not ret:
144 144 raise Exception('Could not get application settings !')
145 145 settings = {}
146 146 for each in ret:
147 147 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
148 148
149 149 return settings
150 150
151 151 def get_hg_ui_settings():
152 152 try:
153 153 sa = meta.Session
154 154 ret = sa.query(HgAppUi).all()
155 155 finally:
156 156 meta.Session.remove()
157 157
158 158 if not ret:
159 159 raise Exception('Could not get application ui settings !')
160 160 settings = {}
161 161 for each in ret:
162 162 k = each.ui_key
163 163 v = each.ui_value
164 164 if k == '/':
165 165 k = 'root_path'
166 166
167 167 if k.find('.') != -1:
168 168 k = k.replace('.', '_')
169 169
170 170 if each.ui_section == 'hooks':
171 171 v = each.ui_active
172 172
173 173 settings[each.ui_section + '_' + k] = v
174 174
175 175 return settings
176 176
177 177 #propagated from mercurial documentation
178 178 ui_sections = ['alias', 'auth',
179 179 'decode/encode', 'defaults',
180 180 'diff', 'email',
181 181 'extensions', 'format',
182 182 'merge-patterns', 'merge-tools',
183 183 'hooks', 'http_proxy',
184 184 'smtp', 'patch',
185 185 'paths', 'profiling',
186 186 'server', 'trusted',
187 187 'ui', 'web', ]
188 188
189 189 def make_ui(read_from='file', path=None, checkpaths=True):
190 190 """
191 191 A function that will read python rc files or database
192 192 and make an mercurial ui object from read options
193 193
194 194 @param path: path to mercurial config file
195 195 @param checkpaths: check the path
196 196 @param read_from: read from 'file' or 'db'
197 197 """
198 198
199 199 baseui = ui.ui()
200 200
201 201 if read_from == 'file':
202 202 if not os.path.isfile(path):
203 203 log.warning('Unable to read config file %s' % path)
204 204 return False
205 205 log.debug('reading hgrc from %s', path)
206 206 cfg = config.config()
207 207 cfg.read(path)
208 208 for section in ui_sections:
209 209 for k, v in cfg.items(section):
210 210 baseui.setconfig(section, k, v)
211 211 log.debug('settings ui from file[%s]%s:%s', section, k, v)
212 212 if checkpaths:check_repo_dir(cfg.items('paths'))
213 213
214 214
215 215 elif read_from == 'db':
216 216 hg_ui = get_hg_ui_cached()
217 217 for ui_ in hg_ui:
218 218 if ui_.ui_active:
219 219 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
220 220 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
221 221
222 222
223 223 return baseui
224 224
225 225
226 226 def set_hg_app_config(config):
227 227 hgsettings = get_hg_settings()
228 228
229 229 for k, v in hgsettings.items():
230 230 config[k] = v
231 231
232 232 def invalidate_cache(name, *args):
233 233 """Invalidates given name cache"""
234 234
235 235 from beaker.cache import region_invalidate
236 236 log.info('INVALIDATING CACHE FOR %s', name)
237 237
238 238 """propagate our arguments to make sure invalidation works. First
239 239 argument has to be the name of cached func name give to cache decorator
240 240 without that the invalidation would not work"""
241 241 tmp = [name]
242 242 tmp.extend(args)
243 243 args = tuple(tmp)
244 244
245 245 if name == 'cached_repo_list':
246 246 from pylons_app.model.hg_model import _get_repos_cached
247 247 region_invalidate(_get_repos_cached, None, *args)
248 248
249 249 if name == 'full_changelog':
250 250 from pylons_app.model.hg_model import _full_changelog_cached
251 251 region_invalidate(_full_changelog_cached, None, *args)
252 252
253 253 class EmptyChangeset(BaseChangeset):
254 254
255 255 revision = -1
256 256 message = ''
257 257 author = ''
258 258
259 259 @LazyProperty
260 260 def raw_id(self):
261 261 """
262 262 Returns raw string identifing this changeset, useful for web
263 263 representation.
264 264 """
265 265 return '0' * 40
266 266
267 267 @LazyProperty
268 268 def short_id(self):
269 269 return self.raw_id[:12]
270 270
271 271 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
272 272 """
273 273 maps all found repositories into db
274 274 """
275 275 from pylons_app.model.repo_model import RepoModel
276 276
277 277 sa = meta.Session
278 278 user = sa.query(User).filter(User.admin == True).first()
279 279
280 280 rm = RepoModel()
281 281
282 282 for name, repo in initial_repo_list.items():
283 283 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
284 284 log.info('repository %s not found creating default', name)
285 285
286 286 form_data = {
287 287 'repo_name':name,
288 288 'description':repo.description if repo.description != 'unknown' else \
289 289 'auto description for %s' % name,
290 290 'private':False
291 291 }
292 292 rm.create(form_data, user, just_db=True)
293 293
294 294
295 295 if remove_obsolete:
296 296 #remove from database those repositories that are not in the filesystem
297 297 for repo in sa.query(Repository).all():
298 298 if repo.repo_name not in initial_repo_list.keys():
299 299 sa.delete(repo)
300 300 sa.commit()
301 301
302 302
303 303 meta.Session.remove()
304 304
305 305 from UserDict import DictMixin
306 306
307 307 class OrderedDict(dict, DictMixin):
308 308
309 309 def __init__(self, *args, **kwds):
310 310 if len(args) > 1:
311 311 raise TypeError('expected at most 1 arguments, got %d' % len(args))
312 312 try:
313 313 self.__end
314 314 except AttributeError:
315 315 self.clear()
316 316 self.update(*args, **kwds)
317 317
318 318 def clear(self):
319 319 self.__end = end = []
320 320 end += [None, end, end] # sentinel node for doubly linked list
321 321 self.__map = {} # key --> [key, prev, next]
322 322 dict.clear(self)
323 323
324 324 def __setitem__(self, key, value):
325 325 if key not in self:
326 326 end = self.__end
327 327 curr = end[1]
328 328 curr[2] = end[1] = self.__map[key] = [key, curr, end]
329 329 dict.__setitem__(self, key, value)
330 330
331 331 def __delitem__(self, key):
332 332 dict.__delitem__(self, key)
333 333 key, prev, next = self.__map.pop(key)
334 334 prev[2] = next
335 335 next[1] = prev
336 336
337 337 def __iter__(self):
338 338 end = self.__end
339 339 curr = end[2]
340 340 while curr is not end:
341 341 yield curr[0]
342 342 curr = curr[2]
343 343
344 344 def __reversed__(self):
345 345 end = self.__end
346 346 curr = end[1]
347 347 while curr is not end:
348 348 yield curr[0]
349 349 curr = curr[1]
350 350
351 351 def popitem(self, last=True):
352 352 if not self:
353 353 raise KeyError('dictionary is empty')
354 354 if last:
355 355 key = reversed(self).next()
356 356 else:
357 357 key = iter(self).next()
358 358 value = self.pop(key)
359 359 return key, value
360 360
361 361 def __reduce__(self):
362 362 items = [[k, self[k]] for k in self]
363 363 tmp = self.__map, self.__end
364 364 del self.__map, self.__end
365 365 inst_dict = vars(self).copy()
366 366 self.__map, self.__end = tmp
367 367 if inst_dict:
368 368 return (self.__class__, (items,), inst_dict)
369 369 return self.__class__, (items,)
370 370
371 371 def keys(self):
372 372 return list(self)
373 373
374 374 setdefault = DictMixin.setdefault
375 375 update = DictMixin.update
376 376 pop = DictMixin.pop
377 377 values = DictMixin.values
378 378 items = DictMixin.items
379 379 iterkeys = DictMixin.iterkeys
380 380 itervalues = DictMixin.itervalues
381 381 iteritems = DictMixin.iteritems
382 382
383 383 def __repr__(self):
384 384 if not self:
385 385 return '%s()' % (self.__class__.__name__,)
386 386 return '%s(%r)' % (self.__class__.__name__, self.items())
387 387
388 388 def copy(self):
389 389 return self.__class__(self)
390 390
391 391 @classmethod
392 392 def fromkeys(cls, iterable, value=None):
393 393 d = cls()
394 394 for key in iterable:
395 395 d[key] = value
396 396 return d
397 397
398 398 def __eq__(self, other):
399 399 if isinstance(other, OrderedDict):
400 400 return len(self) == len(other) and self.items() == other.items()
401 401 return dict.__eq__(self, other)
402 402
403 403 def __ne__(self, other):
404 404 return not self == other
405 405
406 406
407 407 #===============================================================================
408 408 # TEST FUNCTIONS
409 409 #===============================================================================
410 410 def create_test_index(repo_location, full_index):
411 411 """Makes default test index
412 412 @param repo_location:
413 413 @param full_index:
414 414 """
415 415 from pylons_app.lib.indexers.daemon import WhooshIndexingDaemon
416 416 from pylons_app.lib.pidlock import DaemonLock, LockHeld
417 417 from pylons_app.lib.indexers import IDX_LOCATION
418 418 import shutil
419 419
420 420 if os.path.exists(IDX_LOCATION):
421 421 shutil.rmtree(IDX_LOCATION)
422 422
423 423 try:
424 424 l = DaemonLock()
425 425 WhooshIndexingDaemon(repo_location=repo_location)\
426 426 .run(full_index=full_index)
427 427 l.release()
428 428 except LockHeld:
429 429 pass
430 430
431 431 def create_test_env(repos_test_path, config):
432 432 """Makes a fresh database and
433 433 install test repository into tmp dir
434 434 """
435 435 from pylons_app.lib.db_manage import DbManage
436 436 import tarfile
437 437 import shutil
438 438 from os.path import dirname as dn, join as jn, abspath
439 439
440 440 log = logging.getLogger('TestEnvCreator')
441 441 # create logger
442 442 log.setLevel(logging.DEBUG)
443 443 log.propagate = True
444 444 # create console handler and set level to debug
445 445 ch = logging.StreamHandler()
446 446 ch.setLevel(logging.DEBUG)
447 447
448 448 # create formatter
449 449 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
450 450
451 451 # add formatter to ch
452 452 ch.setFormatter(formatter)
453 453
454 454 # add ch to logger
455 455 log.addHandler(ch)
456 456
457 457 #PART ONE create db
458 458 log.debug('making test db')
459 459 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
460 460 dbmanage = DbManage(log_sql=True, dbname=dbname, tests=True)
461 461 dbmanage.create_tables(override=True)
462 462 dbmanage.config_prompt(repos_test_path)
463 463 dbmanage.create_default_user()
464 464 dbmanage.admin_prompt()
465 465 dbmanage.create_permissions()
466 466 dbmanage.populate_default_permissions()
467 467
468 468 #PART TWO make test repo
469 469 log.debug('making test vcs repo')
470 470 if os.path.isdir('/tmp/vcs_test'):
471 471 shutil.rmtree('/tmp/vcs_test')
472 472
473 473 cur_dir = dn(dn(abspath(__file__)))
474 474 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
475 475 tar.extractall('/tmp')
476 476 tar.close()
@@ -1,138 +1,139
1 1 from pylons_app.model.meta import Base
2 2 from sqlalchemy import *
3 3 from sqlalchemy.orm import relation, backref
4 4 from sqlalchemy.orm.session import Session
5 5 from vcs.utils.lazy import LazyProperty
6 6 import logging
7 7
8 8 log = logging.getLogger(__name__)
9 9
10 10 class HgAppSettings(Base):
11 11 __tablename__ = 'hg_app_settings'
12 12 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
13 13 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
14 14 app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
15 15 app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
16 16
17 17 class HgAppUi(Base):
18 18 __tablename__ = 'hg_app_ui'
19 19 __table_args__ = {'useexisting':True}
20 20 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
21 21 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
22 22 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
23 23 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
24 24 ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True)
25 25
26 26
27 27 class User(Base):
28 28 __tablename__ = 'users'
29 29 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
30 30 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
31 31 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
32 32 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
33 33 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
34 34 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
35 35 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
36 36 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
37 37 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
38 38 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
39 39
40 40 user_log = relation('UserLog')
41 41 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id")
42 42
43 43 @LazyProperty
44 44 def full_contact(self):
45 45 return '%s %s <%s>' % (self.name, self.lastname, self.email)
46 46
47 47 def __repr__(self):
48 48 return "<User('id:%s:%s')>" % (self.user_id, self.username)
49 49
50 50 def update_lastlogin(self):
51 51 """Update user lastlogin"""
52 52 import datetime
53 53
54 54 try:
55 55 session = Session.object_session(self)
56 56 self.last_login = datetime.datetime.now()
57 57 session.add(self)
58 58 session.commit()
59 59 log.debug('updated user %s lastlogin', self.username)
60 60 except Exception:
61 61 session.rollback()
62 62
63 63
64 64 class UserLog(Base):
65 65 __tablename__ = 'user_logs'
66 66 __table_args__ = {'useexisting':True}
67 67 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
68 68 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
69 repository_id = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
69 repository_id = Column("repository_id", INTEGER(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
70 repository_name = Column("repository_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 71 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 72 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 73 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
73 74 revision = Column('revision', TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
74 75
75 76 user = relation('User')
76 77 repository = relation('Repository')
77 78
78 79 class Repository(Base):
79 80 __tablename__ = 'repositories'
80 81 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
81 82 repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
82 83 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
83 84 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
84 85 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
85 86 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
86 87 fork_id = Column("fork_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None)
87 88
88 89 user = relation('User')
89 90 fork = relation('Repository', remote_side=repo_id)
90 91 repo_to_perm = relation('RepoToPerm', cascade='all')
91 92
92 93 def __repr__(self):
93 94 return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name)
94 95
95 96 class Permission(Base):
96 97 __tablename__ = 'permissions'
97 98 __table_args__ = {'useexisting':True}
98 99 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
99 100 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
100 101 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
101 102
102 103 def __repr__(self):
103 104 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
104 105
105 106 class RepoToPerm(Base):
106 107 __tablename__ = 'repo_to_perm'
107 108 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
108 109 repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
109 110 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
110 111 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
111 112 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
112 113
113 114 user = relation('User')
114 115 permission = relation('Permission')
115 116 repository = relation('Repository')
116 117
117 118 class UserToPerm(Base):
118 119 __tablename__ = 'user_to_perm'
119 120 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
120 121 user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
121 122 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
122 123 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
123 124
124 125 user = relation('User')
125 126 permission = relation('Permission')
126 127
127 128 class Statistics(Base):
128 129 __tablename__ = 'statistics'
129 130 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
130 131 stat_id = Column("stat_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
131 132 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None)
132 133 stat_on_revision = Column("stat_on_revision", INTEGER(), nullable=False)
133 134 commit_activity = Column("commit_activity", BLOB(), nullable=False)#JSON data
134 135 commit_activity_combined = Column("commit_activity_combined", BLOB(), nullable=False)#JSON data
135 136 languages = Column("languages", BLOB(), nullable=False)#JSON data
136 137
137 138 repository = relation('Repository')
138 139
@@ -1,48 +1,54
1 1 ## -*- coding: utf-8 -*-
2 2 %if c.users_log:
3 3 <table>
4 4 <tr>
5 5 <th class="left">${_('Username')}</th>
6 6 <th class="left">${_('Repository')}</th>
7 7 <th class="left">${_('Action')}</th>
8 8 <th class="left">${_('Date')}</th>
9 9 <th class="left">${_('From IP')}</th>
10 10 </tr>
11 11
12 12 %for cnt,l in enumerate(c.users_log):
13 13 <tr class="parity${cnt%2}">
14 14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
15 <td>${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}</td>
15 <td>
16 %if l.repository:
17 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
18 %else:
19 ${l.repository_name}
20 %endif
21 </td>
16 22 <td>
17 23 % if l.action == 'push' and l.revision:
18 24 ${h.link_to('%s - %s' % (l.action,l.revision),
19 25 h.url('changeset_home',repo_name=l.repository.repo_name,revision=l.revision))}
20 26 %else:
21 27 ${l.action}
22 28 %endif
23 29 </td>
24 30 <td>${l.action_date}</td>
25 31 <td>${l.user_ip}</td>
26 32 </tr>
27 33 %endfor
28 34 </table>
29 35
30 36 <script type="text/javascript">
31 37 var data_div = 'user_log';
32 38 YAHOO.util.Event.onDOMReady(function(){
33 39 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
34 40 YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});});
35 41 </script>
36 42
37 43
38 44 <div class="pagination-wh pagination-left">
39 45 ${c.users_log.pager('$link_previous ~2~ $link_next',
40 46 onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{
41 47 success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText;
42 48 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
43 49 YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');});
44 50 YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")}
45 51 </div>
46 52 %else:
47 53 ${_('No actions yet')}
48 54 %endif
General Comments 0
You need to be logged in to leave comments. Login now