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