##// END OF EJS Templates
routes python 2.5 compatible...
marcink -
r371:5cd6616b default
parent child Browse files
Show More
@@ -0,0 +1,79 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('User administration')}
6 </%def>
7
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
10 </%def>
11
12 <%def name="page_nav()">
13 ${self.menu('admin')}
14 </%def>
15
16 <%def name="main()">
17 <div class="box">
18 <!-- box / title -->
19 <div class="title">
20 ${self.breadcrumbs()}
21 </div>
22 <!-- end box / title -->
23 ${h.form(url('admin_settings_my_account_update'),method='put')}
24 <div class="form">
25 <!-- fields -->
26 <div class="fields">
27 <div class="field">
28 <div class="label">
29 <label for="username">${_('Username')}:</label>
30 </div>
31 <div class="input">
32 ${h.text('username')}
33 </div>
34 </div>
35
36 <div class="field">
37 <div class="label">
38 <label for="new_password">${_('New password')}:</label>
39 </div>
40 <div class="input">
41 ${h.password('new_password')}
42 </div>
43 </div>
44
45 <div class="field">
46 <div class="label">
47 <label for="name">${_('Name')}:</label>
48 </div>
49 <div class="input">
50 ${h.text('name')}
51 </div>
52 </div>
53
54 <div class="field">
55 <div class="label">
56 <label for="lastname">${_('Lastname')}:</label>
57 </div>
58 <div class="input">
59 ${h.text('lastname')}
60 </div>
61 </div>
62
63 <div class="field">
64 <div class="label">
65 <label for="email">${_('Email')}:</label>
66 </div>
67 <div class="input">
68 ${h.text('email')}
69 </div>
70 </div>
71
72 <div class="buttons">
73 ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")}
74 </div>
75 </div>
76 </div>
77 ${h.end_form()}
78 </div>
79 </%def> No newline at end of file
@@ -1,131 +1,161 b''
1 1 """Routes configuration
2 2
3 3 The more specific and detailed routes should be defined first so they
4 4 may take precedent over the more generic routes. For more information
5 5 refer to the routes manual at http://routes.groovie.org/docs/
6 6 """
7 from __future__ import with_statement
7 8 from routes import Mapper
8 9 from pylons_app.lib.utils import check_repo_fast as cr
9 10
10 11 def make_map(config):
11 12 """Create, configure and return the routes Mapper"""
12 13 map = Mapper(directory=config['pylons.paths']['controllers'],
13 14 always_scan=config['debug'])
14 15 map.minimization = False
15 16 map.explicit = False
16 17
17 18 # The ErrorController route (handles 404/500 error pages); it should
18 19 # likely stay at the top, ensuring it can always be resolved
19 20 map.connect('/error/{action}', controller='error')
20 21 map.connect('/error/{action}/{id}', controller='error')
21 22
22 23 # CUSTOM ROUTES HERE
23 24 map.connect('hg_home', '/', controller='hg', action='index')
24 25
25 26 def check_repo(environ, match_dict):
26 27 """
27 28 check for valid repository for proper 404 handling
28 29 @param environ:
29 30 @param match_dict:
30 31 """
31 32 repo_name = match_dict.get('repo_name')
32 33 return not cr(repo_name, config['base_path'])
33 34
34 #REST routes
35 #REST REPO MAP
35 36 with map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
36 37 m.connect("repos", "/repos",
37 38 action="create", conditions=dict(method=["POST"]))
38 39 m.connect("repos", "/repos",
39 40 action="index", conditions=dict(method=["GET"]))
40 41 m.connect("formatted_repos", "/repos.{format}",
41 42 action="index",
42 43 conditions=dict(method=["GET"]))
43 44 m.connect("new_repo", "/repos/new",
44 45 action="new", conditions=dict(method=["GET"]))
45 46 m.connect("formatted_new_repo", "/repos/new.{format}",
46 47 action="new", conditions=dict(method=["GET"]))
47 48 m.connect("/repos/{repo_name:.*}",
48 49 action="update", conditions=dict(method=["PUT"],
49 50 function=check_repo))
50 51 m.connect("/repos/{repo_name:.*}",
51 52 action="delete", conditions=dict(method=["DELETE"],
52 53 function=check_repo))
53 54 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
54 55 action="edit", conditions=dict(method=["GET"],
55 56 function=check_repo))
56 57 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
57 58 action="edit", conditions=dict(method=["GET"],
58 59 function=check_repo))
59 60 m.connect("repo", "/repos/{repo_name:.*}",
60 61 action="show", conditions=dict(method=["GET"],
61 62 function=check_repo))
62 63 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
63 64 action="show", conditions=dict(method=["GET"],
64 65 function=check_repo))
65 66 #ajax delete repo perm user
66 67 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
67 68 action="delete_perm_user", conditions=dict(method=["DELETE"],
68 69 function=check_repo))
69 70
70 71 map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
71 72 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
72 map.resource('setting', 'settings', controller='admin/settings', path_prefix='/_admin', name_prefix='admin_')
73
74 #map.resource('setting', 'settings', controller='admin/settings', path_prefix='/_admin', name_prefix='admin_')
75 #REST SETTINGS MAP
76 with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
77 m.connect("admin_settings", "/settings",
78 action="create", conditions=dict(method=["POST"]))
79 m.connect("admin_settings", "/settings",
80 action="index", conditions=dict(method=["GET"]))
81 m.connect("admin_formatted_settings", "/settings.{format}",
82 action="index", conditions=dict(method=["GET"]))
83 m.connect("admin_new_setting", "/settings/new",
84 action="new", conditions=dict(method=["GET"]))
85 m.connect("admin_formatted_new_setting", "/settings/new.{format}",
86 action="new", conditions=dict(method=["GET"]))
87 m.connect("/settings/{setting_id}",
88 action="update", conditions=dict(method=["PUT"]))
89 m.connect("/settings/{setting_id}",
90 action="delete", conditions=dict(method=["DELETE"]))
91 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
92 action="edit", conditions=dict(method=["GET"]))
93 m.connect("admin_formatted_edit_setting", "/settings/{setting_id}.{format}/edit",
94 action="edit", conditions=dict(method=["GET"]))
95 m.connect("admin_setting", "/settings/{setting_id}",
96 action="show", conditions=dict(method=["GET"]))
97 m.connect("admin_formatted_setting", "/settings/{setting_id}.{format}",
98 action="show", conditions=dict(method=["GET"]))
99 m.connect("admin_settings_my_account", "/my_account",
100 action="my_account", conditions=dict(method=["GET"]))
101 m.connect("admin_settings_my_account_update", "/my_account_update",
102 action="my_account_update", conditions=dict(method=["PUT"]))
73 103
74 104 #ADMIN
75 105 with map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
76 106 m.connect('admin_home', '', action='index')#main page
77 107 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
78 108 action='add_repo')
79 109
80 110 #LOGIN/LOGOUT
81 111 map.connect('login_home', '/_admin/login', controller='login')
82 112 map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
83 113 map.connect('register', '/_admin/register', controller='login', action='register')
84 114
85 115 #FEEDS
86 116 map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
87 117 controller='feed', action='rss',
88 118 conditions=dict(function=check_repo))
89 119 map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
90 120 controller='feed', action='atom',
91 121 conditions=dict(function=check_repo))
92 122
93 123
94 124 #OTHERS
95 125 map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
96 126 controller='changeset', revision='tip',
97 127 conditions=dict(function=check_repo))
98 128 map.connect('summary_home', '/{repo_name:.*}/summary',
99 129 controller='summary', conditions=dict(function=check_repo))
100 130 map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
101 131 controller='shortlog', conditions=dict(function=check_repo))
102 132 map.connect('branches_home', '/{repo_name:.*}/branches',
103 133 controller='branches', conditions=dict(function=check_repo))
104 134 map.connect('tags_home', '/{repo_name:.*}/tags',
105 135 controller='tags', conditions=dict(function=check_repo))
106 136 map.connect('changelog_home', '/{repo_name:.*}/changelog',
107 137 controller='changelog', conditions=dict(function=check_repo))
108 138 map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
109 139 controller='files', revision='tip', f_path='',
110 140 conditions=dict(function=check_repo))
111 141 map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
112 142 controller='files', action='diff', revision='tip', f_path='',
113 143 conditions=dict(function=check_repo))
114 144 map.connect('files_raw_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
115 145 controller='files', action='rawfile', revision='tip', f_path='',
116 146 conditions=dict(function=check_repo))
117 147 map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
118 148 controller='files', action='annotate', revision='tip', f_path='',
119 149 conditions=dict(function=check_repo))
120 150 map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
121 151 controller='files', action='archivefile', revision='tip',
122 152 conditions=dict(function=check_repo))
123 153 map.connect('repo_settings_update', '/{repo_name:.*}/settings',
124 154 controller='settings', action="update",
125 155 conditions=dict(method=["PUT"], function=check_repo))
126 156 map.connect('repo_settings_home', '/{repo_name:.*}/settings',
127 157 controller='settings', action='index',
128 158 conditions=dict(function=check_repo))
129 159
130 160
131 161 return map
@@ -1,210 +1,211 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # repos controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 6 # This program is free software; you can redistribute it and/or
6 7 # modify it under the terms of the GNU General Public License
7 8 # as published by the Free Software Foundation; version 2
8 9 # of the License or (at your opinion) any later version of the license.
9 10 #
10 11 # This program is distributed in the hope that it will be useful,
11 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 14 # GNU General Public License for more details.
14 15 #
15 16 # You should have received a copy of the GNU General Public License
16 17 # along with this program; if not, write to the Free Software
17 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 19 # MA 02110-1301, USA.
19 20 """
20 21 Created on April 7, 2010
21 22 admin controller for pylons
22 23 @author: marcink
23 24 """
24 25 from formencode import htmlfill
25 26 from operator import itemgetter
26 27 from pylons import request, response, session, tmpl_context as c, url
27 28 from pylons.controllers.util import abort, redirect
28 29 from pylons.i18n.translation import _
29 30 from pylons_app.lib import helpers as h
30 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 32 from pylons_app.lib.base import BaseController, render
32 33 from pylons_app.lib.utils import invalidate_cache
33 34 from pylons_app.model.forms import RepoForm
34 35 from pylons_app.model.hg_model import HgModel
35 36 from pylons_app.model.repo_model import RepoModel
36 37 import formencode
37 38 import logging
38 39 import traceback
39 40 from paste.httpexceptions import HTTPInternalServerError
40 41
41 42 log = logging.getLogger(__name__)
42 43
43 44 class ReposController(BaseController):
44 45 """REST Controller styled on the Atom Publishing Protocol"""
45 46 # To properly map this controller, ensure your config/routing.py
46 47 # file has a resource setup:
47 48 # map.resource('repo', 'repos')
48 49
49 50 @LoginRequired()
50 51 @HasPermissionAllDecorator('hg.admin')
51 52 def __before__(self):
52 53 c.admin_user = session.get('admin_user')
53 54 c.admin_username = session.get('admin_username')
54 55 super(ReposController, self).__before__()
55 56
56 57 def index(self, format='html'):
57 58 """GET /repos: All items in the collection"""
58 59 # url('repos')
59 60 cached_repo_list = HgModel().get_repos()
60 61 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
61 62 return render('admin/repos/repos.html')
62 63
63 64 def create(self):
64 65 """POST /repos: Create a new item"""
65 66 # url('repos')
66 67 repo_model = RepoModel()
67 68 _form = RepoForm()()
68 69 form_result = {}
69 70 try:
70 71 form_result = _form.to_python(dict(request.POST))
71 72 repo_model.create(form_result, c.hg_app_user)
72 73 invalidate_cache('cached_repo_list')
73 74 h.flash(_('created repository %s') % form_result['repo_name'],
74 75 category='success')
75 76
76 77 except formencode.Invalid as errors:
77 78 c.new_repo = errors.value['repo_name']
78 79 return htmlfill.render(
79 80 render('admin/repos/repo_add.html'),
80 81 defaults=errors.value,
81 82 errors=errors.error_dict or {},
82 83 prefix_error=False,
83 84 encoding="UTF-8")
84 85
85 86 except Exception:
86 87 log.error(traceback.format_exc())
87 88 msg = _('error occured during creation of repository %s') \
88 89 % form_result.get('repo_name')
89 90 h.flash(msg, category='error')
90 91
91 92 return redirect('repos')
92 93
93 94 def new(self, format='html'):
94 95 """GET /repos/new: Form to create a new item"""
95 96 new_repo = request.GET.get('repo', '')
96 97 c.new_repo = h.repo_name_slug(new_repo)
97 98
98 99 return render('admin/repos/repo_add.html')
99 100
100 101 def update(self, repo_name):
101 102 """PUT /repos/repo_name: Update an existing item"""
102 103 # Forms posted to this method should contain a hidden field:
103 104 # <input type="hidden" name="_method" value="PUT" />
104 105 # Or using helpers:
105 106 # h.form(url('repo', repo_name=ID),
106 107 # method='put')
107 108 # url('repo', repo_name=ID)
108 109 repo_model = RepoModel()
109 110 changed_name = repo_name
110 111 _form = RepoForm(edit=True, old_data={'repo_name':repo_name})()
111 112
112 113 try:
113 114 form_result = _form.to_python(dict(request.POST))
114 115 repo_model.update(repo_name, form_result)
115 116 invalidate_cache('cached_repo_list')
116 117 h.flash(_('Repository %s updated succesfully' % repo_name),
117 118 category='success')
118 119 changed_name = form_result['repo_name']
119 120 except formencode.Invalid as errors:
120 121 c.repo_info = repo_model.get(repo_name)
121 122 c.users_array = repo_model.get_users_js()
122 123 errors.value.update({'user':c.repo_info.user.username})
123 124 return htmlfill.render(
124 125 render('admin/repos/repo_edit.html'),
125 126 defaults=errors.value,
126 127 errors=errors.error_dict or {},
127 128 prefix_error=False,
128 129 encoding="UTF-8")
129 130
130 131 except Exception:
131 132 log.error(traceback.format_exc())
132 133 h.flash(_('error occured during update of repository %s') \
133 134 % repo_name, category='error')
134 135
135 136 return redirect(url('edit_repo', repo_name=changed_name))
136 137
137 138 def delete(self, repo_name):
138 139 """DELETE /repos/repo_name: Delete an existing item"""
139 140 # Forms posted to this method should contain a hidden field:
140 141 # <input type="hidden" name="_method" value="DELETE" />
141 142 # Or using helpers:
142 143 # h.form(url('repo', repo_name=ID),
143 144 # method='delete')
144 145 # url('repo', repo_name=ID)
145 146
146 147 repo_model = RepoModel()
147 148 repo = repo_model.get(repo_name)
148 149 if not repo:
149 150 h.flash(_('%s repository is not mapped to db perhaps'
150 151 ' it was moved or renamed from the filesystem'
151 152 ' please run the application again'
152 153 ' in order to rescan repositories') % repo_name,
153 154 category='error')
154 155
155 156 return redirect(url('repos'))
156 157 try:
157 158 repo_model.delete(repo)
158 159 invalidate_cache('cached_repo_list')
159 160 h.flash(_('deleted repository %s') % repo_name, category='success')
160 161 except Exception:
161 162 h.flash(_('An error occured during deletion of %s') % repo_name,
162 163 category='error')
163 164
164 165 return redirect(url('repos'))
165 166
166 167 def delete_perm_user(self, repo_name):
167 168 """
168 169 DELETE an existing repository permission user
169 170 @param repo_name:
170 171 """
171 172
172 173 try:
173 174 repo_model = RepoModel()
174 175 repo_model.delete_perm_user(request.POST, repo_name)
175 176 except Exception as e:
176 177 h.flash(_('An error occured during deletion of repository user'),
177 178 category='error')
178 179 raise HTTPInternalServerError()
179 180
180 181 def show(self, repo_name, format='html'):
181 182 """GET /repos/repo_name: Show a specific item"""
182 183 # url('repo', repo_name=ID)
183 184
184 185 def edit(self, repo_name, format='html'):
185 186 """GET /repos/repo_name/edit: Form to edit an existing item"""
186 187 # url('edit_repo', repo_name=ID)
187 188 repo_model = RepoModel()
188 189 c.repo_info = repo = repo_model.get(repo_name)
189 190 if not repo:
190 191 h.flash(_('%s repository is not mapped to db perhaps'
191 192 ' it was created or renamed from the filesystem'
192 193 ' please run the application again'
193 194 ' in order to rescan repositories') % repo_name,
194 195 category='error')
195 196
196 197 return redirect(url('repos'))
197 198 defaults = c.repo_info.__dict__
198 199 defaults.update({'user':c.repo_info.user.username})
199 200 c.users_array = repo_model.get_users_js()
200 201
201 202 for p in c.repo_info.repo2perm:
202 203 defaults.update({'perm_%s' % p.user.username:
203 204 p.permission.permission_name})
204 205
205 206 return htmlfill.render(
206 207 render('admin/repos/repo_edit.html'),
207 208 defaults=defaults,
208 209 encoding="UTF-8",
209 210 force_defaults=False
210 211 )
@@ -1,151 +1,212 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on July 14, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 27 config
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
32 32 from pylons_app.lib.base import BaseController, render
33 33 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
34 34 set_hg_app_config
35 35 from pylons_app.model.db import User, UserLog, HgAppSettings
36 36 from pylons_app.model.forms import UserForm, ApplicationSettingsForm
37 37 from pylons_app.model.hg_model import HgModel
38 38 from pylons_app.model.user_model import UserModel
39 39 import formencode
40 40 import logging
41 41 import traceback
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class SettingsController(BaseController):
47 47 """REST Controller styled on the Atom Publishing Protocol"""
48 48 # To properly map this controller, ensure your config/routing.py
49 49 # file has a resource setup:
50 50 # map.resource('setting', 'settings', controller='admin/settings',
51 51 # path_prefix='/admin', name_prefix='admin_')
52 52
53 53
54 54 @LoginRequired()
55 #@HasPermissionAllDecorator('hg.admin')
56 55 def __before__(self):
57 56 c.admin_user = session.get('admin_user')
58 57 c.admin_username = session.get('admin_username')
59 58 super(SettingsController, self).__before__()
60 59
60
61 @HasPermissionAllDecorator('hg.admin')
61 62 def index(self, format='html'):
62 63 """GET /admin/settings: All items in the collection"""
63 64 # url('admin_settings')
64 65
65 66 hgsettings = self.sa.query(HgAppSettings).scalar()
66 67 defaults = hgsettings.__dict__ if hgsettings else {}
67 68 return htmlfill.render(
68 69 render('admin/settings/settings.html'),
69 70 defaults=defaults,
70 71 encoding="UTF-8",
71 72 force_defaults=False
72 73 )
73 74
75 @HasPermissionAllDecorator('hg.admin')
74 76 def create(self):
75 77 """POST /admin/settings: Create a new item"""
76 78 # url('admin_settings')
77 79
80 @HasPermissionAllDecorator('hg.admin')
78 81 def new(self, format='html'):
79 82 """GET /admin/settings/new: Form to create a new item"""
80 83 # url('admin_new_setting')
81 84
82 def update(self, id):
83 """PUT /admin/settings/id: Update an existing item"""
85 @HasPermissionAllDecorator('hg.admin')
86 def update(self, setting_id):
87 """PUT /admin/settings/setting_id: Update an existing item"""
84 88 # Forms posted to this method should contain a hidden field:
85 89 # <input type="hidden" name="_method" value="PUT" />
86 90 # Or using helpers:
87 # h.form(url('admin_setting', id=ID),
91 # h.form(url('admin_setting', setting_id=ID),
88 92 # method='put')
89 # url('admin_setting', id=ID)
90 if id == 'mapping':
93 # url('admin_setting', setting_id=ID)
94 if setting_id == 'mapping':
91 95 rm_obsolete = request.POST.get('destroy', False)
92 96 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
93 97
94 98 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
95 99 repo2db_mapper(initial, rm_obsolete)
96 100 invalidate_cache('cached_repo_list')
97 101 h.flash(_('Repositories sucessfully rescanned'), category='success')
98 102
99 if id == 'global':
103 if setting_id == 'global':
100 104
101 105 application_form = ApplicationSettingsForm()()
102 106 try:
103 107 form_result = application_form.to_python(dict(request.POST))
104 108 title = form_result['app_title']
105 109 realm = form_result['app_auth_realm']
106 110
107 111 try:
108 112 hgsettings = self.sa.query(HgAppSettings).get(1)
109 113 hgsettings.app_auth_realm = realm
110 114 hgsettings.app_title = title
111 115
112 116 self.sa.add(hgsettings)
113 117 self.sa.commit()
114 118 set_hg_app_config(config)
115 119 h.flash(_('Updated application settings'),
116 120 category='success')
117 121
118 122 except:
119 123 log.error(traceback.format_exc())
120 124 h.flash(_('error occured during chaning application settings'),
121 125 category='error')
122 126
123 127 self.sa.rollback()
124 128
125 129
126 130 except formencode.Invalid as errors:
127 131 return htmlfill.render(
128 132 render('admin/settings/settings.html'),
129 133 defaults=errors.value,
130 134 errors=errors.error_dict or {},
131 135 prefix_error=False,
132 136 encoding="UTF-8")
133 137
134 138 return redirect(url('admin_settings'))
135 139
136 def delete(self, id):
137 """DELETE /admin/settings/id: Delete an existing item"""
140 @HasPermissionAllDecorator('hg.admin')
141 def delete(self, setting_id):
142 """DELETE /admin/settings/setting_id: Delete an existing item"""
138 143 # Forms posted to this method should contain a hidden field:
139 144 # <input type="hidden" name="_method" value="DELETE" />
140 145 # Or using helpers:
141 # h.form(url('admin_setting', id=ID),
146 # h.form(url('admin_setting', setting_id=ID),
142 147 # method='delete')
143 # url('admin_setting', id=ID)
148 # url('admin_setting', setting_id=ID)
149
150 @HasPermissionAllDecorator('hg.admin')
151 def show(self, setting_id, format='html'):
152 """GET /admin/settings/setting_id: Show a specific item"""
153 # url('admin_setting', setting_id=ID)
154
155 @HasPermissionAllDecorator('hg.admin')
156 def edit(self, setting_id, format='html'):
157 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
158 # url('admin_edit_setting', setting_id=ID)
159
160
161 def my_account(self):
162 """
163 GET /_admin/my_account Displays info about my account
164 """
165 # url('admin_settings_my_account')
166 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
167 if c.user.username == 'default':
168 h.flash(_("You can't edit this user since it's"
169 " crucial for entire application"), category='warning')
170 return redirect(url('users'))
171
172 defaults = c.user.__dict__
173 return htmlfill.render(
174 render('admin/users/user_edit_my_account.html'),
175 defaults=defaults,
176 encoding="UTF-8",
177 force_defaults=False
178 )
144 179
145 def show(self, id, format='html'):
146 """GET /admin/settings/id: Show a specific item"""
147 # url('admin_setting', id=ID)
180 def my_account_update(self):
181 """PUT /_admin/my_account_update: Update an existing item"""
182 # Forms posted to this method should contain a hidden field:
183 # <input type="hidden" name="_method" value="PUT" />
184 # Or using helpers:
185 # h.form(url('admin_settings_my_account_update'),
186 # method='put')
187 # url('admin_settings_my_account_update', id=ID)
188 user_model = UserModel()
189 uid = c.hg_app_user.user_id
190 _form = UserForm(edit=True, old_data={'user_id':uid})()
191 form_result = {}
192 try:
193 form_result = _form.to_python(dict(request.POST))
194 user_model.update_my_account(uid, form_result)
195 h.flash(_('Your account was updated succesfully'), category='success')
148 196
149 def edit(self, id, format='html'):
150 """GET /admin/settings/id/edit: Form to edit an existing item"""
151 # url('admin_edit_setting', id=ID)
197 except formencode.Invalid as errors:
198 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
199 return htmlfill.render(
200 render('admin/users/user_edit_my_account.html'),
201 defaults=errors.value,
202 errors=errors.error_dict or {},
203 prefix_error=False,
204 encoding="UTF-8")
205 except Exception:
206 log.error(traceback.format_exc())
207 h.flash(_('error occured during update of user %s') \
208 % form_result.get('username'), category='error')
209
210 return redirect(url('my_account'))
211
212
@@ -1,161 +1,163 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # users controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 """
21 Created on April 4, 2010
22 users controller for pylons
23 @author: marcink
24 """
25
20 26 from formencode import htmlfill
21 27 from pylons import request, session, tmpl_context as c, url
22 28 from pylons.controllers.util import abort, redirect
23 29 from pylons.i18n.translation import _
24 30 from pylons_app.lib import helpers as h
25 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
26 32 from pylons_app.lib.base import BaseController, render
27 33 from pylons_app.model.db import User, UserLog
28 34 from pylons_app.model.forms import UserForm
29 35 from pylons_app.model.user_model import UserModel, DefaultUserException
30 36 import formencode
31 37 import logging
32 38 import traceback
33 """
34 Created on April 4, 2010
35 users controller for pylons
36 @author: marcink
37 """
39
38 40
39 41 log = logging.getLogger(__name__)
40 42
41 43 class UsersController(BaseController):
42 44 """REST Controller styled on the Atom Publishing Protocol"""
43 45 # To properly map this controller, ensure your config/routing.py
44 46 # file has a resource setup:
45 47 # map.resource('user', 'users')
46 48
47 49 @LoginRequired()
48 50 @HasPermissionAllDecorator('hg.admin')
49 51 def __before__(self):
50 52 c.admin_user = session.get('admin_user')
51 53 c.admin_username = session.get('admin_username')
52 54 super(UsersController, self).__before__()
53 55
54 56
55 57 def index(self, format='html'):
56 58 """GET /users: All items in the collection"""
57 59 # url('users')
58 60
59 61 c.users_list = self.sa.query(User).all()
60 62 return render('admin/users/users.html')
61 63
62 64 def create(self):
63 65 """POST /users: Create a new item"""
64 66 # url('users')
65 67
66 68 user_model = UserModel()
67 69 login_form = UserForm()()
68 70 try:
69 71 form_result = login_form.to_python(dict(request.POST))
70 72 user_model.create(form_result)
71 73 h.flash(_('created user %s') % form_result['username'],
72 74 category='success')
73 75 except formencode.Invalid as errors:
74 76 return htmlfill.render(
75 77 render('admin/users/user_add.html'),
76 78 defaults=errors.value,
77 79 errors=errors.error_dict or {},
78 80 prefix_error=False,
79 81 encoding="UTF-8")
80 82 except Exception:
81 83 log.error(traceback.format_exc())
82 84 h.flash(_('error occured during creation of user %s') \
83 85 % request.POST.get('username'), category='error')
84 86 return redirect(url('users'))
85 87
86 88 def new(self, format='html'):
87 89 """GET /users/new: Form to create a new item"""
88 90 # url('new_user')
89 91 return render('admin/users/user_add.html')
90 92
91 93 def update(self, id):
92 94 """PUT /users/id: Update an existing item"""
93 95 # Forms posted to this method should contain a hidden field:
94 96 # <input type="hidden" name="_method" value="PUT" />
95 97 # Or using helpers:
96 98 # h.form(url('user', id=ID),
97 99 # method='put')
98 100 # url('user', id=ID)
99 101 user_model = UserModel()
100 102 _form = UserForm(edit=True, old_data={'user_id':id})()
101 103 form_result = {}
102 104 try:
103 105 form_result = _form.to_python(dict(request.POST))
104 106 user_model.update(id, form_result)
105 107 h.flash(_('User updated succesfully'), category='success')
106 108
107 109 except formencode.Invalid as errors:
108 110 c.user = user_model.get_user(id)
109 111 return htmlfill.render(
110 112 render('admin/users/user_edit.html'),
111 113 defaults=errors.value,
112 114 errors=errors.error_dict or {},
113 115 prefix_error=False,
114 116 encoding="UTF-8")
115 117 except Exception:
116 118 log.error(traceback.format_exc())
117 119 h.flash(_('error occured during update of user %s') \
118 120 % form_result.get('username'), category='error')
119 121
120 122 return redirect(url('users'))
121 123
122 124 def delete(self, id):
123 125 """DELETE /users/id: Delete an existing item"""
124 126 # Forms posted to this method should contain a hidden field:
125 127 # <input type="hidden" name="_method" value="DELETE" />
126 128 # Or using helpers:
127 129 # h.form(url('user', id=ID),
128 130 # method='delete')
129 131 # url('user', id=ID)
130 132 user_model = UserModel()
131 133 try:
132 134 user_model.delete(id)
133 135 h.flash(_('sucessfully deleted user'), category='success')
134 136 except DefaultUserException as e:
135 137 h.flash(str(e), category='warning')
136 138 except Exception:
137 139 h.flash(_('An error occured during deletion of user'),
138 140 category='error')
139 141 return redirect(url('users'))
140 142
141 143 def show(self, id, format='html'):
142 144 """GET /users/id: Show a specific item"""
143 145 # url('user', id=ID)
144 146
145 147
146 148 def edit(self, id, format='html'):
147 149 """GET /users/id/edit: Form to edit an existing item"""
148 150 # url('edit_user', id=ID)
149 151 c.user = self.sa.query(User).get(id)
150 152 if c.user.username == 'default':
151 153 h.flash(_("You can't edit this user since it's"
152 154 " crucial for entire application"), category='warning')
153 155 return redirect(url('users'))
154 156
155 157 defaults = c.user.__dict__
156 158 return htmlfill.render(
157 159 render('admin/users/user_edit.html'),
158 160 defaults=defaults,
159 161 encoding="UTF-8",
160 162 force_defaults=False
161 163 )
@@ -1,419 +1,436 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # authentication and permission libraries
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 4, 2010
22 22
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from functools import wraps
27 27 from pylons import config, session, url, request
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons_app.lib.utils import get_repo_slug
30 30 from pylons_app.model import meta
31 31 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
32 32 from sqlalchemy.exc import OperationalError
33 33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 34 import crypt
35 35 import logging
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 def get_crypt_password(password):
40 40 """
41 41 Cryptographic function used for password hashing
42 42 @param password: password to hash
43 43 """
44 44 return crypt.crypt(password, '6a')
45 45
46 46
47 47 @cache_region('super_short_term', 'cached_user')
48 48 def get_user_cached(username):
49 49 sa = meta.Session
50 50 try:
51 51 user = sa.query(User).filter(User.username == username).one()
52 52 finally:
53 53 meta.Session.remove()
54 54 return user
55 55
56 56 def authfunc(environ, username, password):
57 57 password_crypt = get_crypt_password(password)
58 58 try:
59 59 user = get_user_cached(username)
60 60 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
61 61 log.error(e)
62 62 user = None
63 63
64 64 if user:
65 65 if user.active:
66 66 if user.username == username and user.password == password_crypt:
67 67 log.info('user %s authenticated correctly', username)
68 68 return True
69 69 else:
70 70 log.error('user %s is disabled', username)
71 71
72 72 return False
73 73
74 74 class AuthUser(object):
75 75 """
76 76 A simple object that handles a mercurial username for authentication
77 77 """
78 78 def __init__(self):
79 79 self.username = 'None'
80 80 self.name = ''
81 81 self.lastname = ''
82 82 self.user_id = None
83 83 self.is_authenticated = False
84 84 self.is_admin = False
85 85 self.permissions = {}
86 86
87 87
88 88 def set_available_permissions(config):
89 89 """
90 90 This function will propagate pylons globals with all available defined
91 91 permission given in db. We don't wannt to check each time from db for new
92 92 permissions since adding a new permission also requires application restart
93 93 ie. to decorate new views with the newly created permission
94 94 @param config:
95 95 """
96 96 log.info('getting information about all available permissions')
97 97 try:
98 98 sa = meta.Session
99 99 all_perms = sa.query(Permission).all()
100 100 finally:
101 101 meta.Session.remove()
102 102
103 103 config['available_permissions'] = [x.permission_name for x in all_perms]
104 104
105 105 def set_base_path(config):
106 106 config['base_path'] = config['pylons.app_globals'].base_path
107 107
108 def fill_data(user):
109 """
110 Fills user data with those from database
111 @param user:
112 """
113 sa = meta.Session
114 dbuser = sa.query(User).get(user.user_id)
115
116 user.username = dbuser.username
117 user.is_admin = dbuser.admin
118 user.name = dbuser.name
119 user.lastname = dbuser.lastname
120
121 meta.Session.remove()
122 return user
123
108 124 def fill_perms(user):
109 125 """
110 126 Fills user permission attribute with permissions taken from database
111 127 @param user:
112 128 """
113 129
114 130 sa = meta.Session
115 131 user.permissions['repositories'] = {}
132 user.permissions['global'] = set()
116 133
117 134 #first fetch default permissions
118 135 default_perms = sa.query(Repo2Perm, Repository, Permission)\
119 136 .join((Repository, Repo2Perm.repository_id == Repository.repo_id))\
120 137 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
121 138 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
122 139 'default').one().user_id).all()
123 140
124 141 if user.is_admin:
125 user.permissions['global'] = set(['hg.admin'])
142 user.permissions['global'].add('hg.admin')
126 143 #admin have all rights full
127 144 for perm in default_perms:
128 145 p = 'repository.admin'
129 146 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
130 147
131 148 else:
132 user.permissions['global'] = set()
149 user.permissions['global'].add('')
133 150 for perm in default_perms:
134 151 if perm.Repository.private:
135 152 #disable defaults for private repos,
136 153 p = 'repository.none'
137 154 elif perm.Repository.user_id == user.user_id:
138 155 #set admin if owner
139 156 p = 'repository.admin'
140 157 else:
141 158 p = perm.Permission.permission_name
142 159
143 160 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
144 161
145 162
146 163 user_perms = sa.query(Repo2Perm, Permission, Repository)\
147 164 .join((Repository, Repo2Perm.repository_id == Repository.repo_id))\
148 165 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
149 166 .filter(Repo2Perm.user_id == user.user_id).all()
150 167 #overwrite userpermissions with defaults
151 168 for perm in user_perms:
152 169 #set write if owner
153 170 if perm.Repository.user_id == user.user_id:
154 171 p = 'repository.write'
155 172 else:
156 173 p = perm.Permission.permission_name
157 174 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
158 175 meta.Session.remove()
159 176 return user
160 177
161 178 def get_user(session):
162 179 """
163 180 Gets user from session, and wraps permissions into user
164 181 @param session:
165 182 """
166 183 user = session.get('hg_app_user', AuthUser())
167
168 184 if user.is_authenticated:
185 user = fill_data(user)
169 186 user = fill_perms(user)
170 187 session['hg_app_user'] = user
171 188 session.save()
172 189 return user
173 190
174 191 #===============================================================================
175 192 # CHECK DECORATORS
176 193 #===============================================================================
177 194 class LoginRequired(object):
178 195 """
179 196 Must be logged in to execute this function else redirect to login page
180 197 """
181 198
182 199 def __call__(self, func):
183 200 @wraps(func)
184 201 def _wrapper(*fargs, **fkwargs):
185 202 user = session.get('hg_app_user', AuthUser())
186 203 log.debug('Checking login required for user:%s', user.username)
187 204 if user.is_authenticated:
188 205 log.debug('user %s is authenticated', user.username)
189 206 func(*fargs)
190 207 else:
191 208 log.warn('user %s not authenticated', user.username)
192 209 log.debug('redirecting to login page')
193 210 return redirect(url('login_home'))
194 211
195 212 return _wrapper
196 213
197 214 class PermsDecorator(object):
198 215 """
199 216 Base class for decorators
200 217 """
201 218
202 219 def __init__(self, *required_perms):
203 220 available_perms = config['available_permissions']
204 221 for perm in required_perms:
205 222 if perm not in available_perms:
206 223 raise Exception("'%s' permission is not defined" % perm)
207 224 self.required_perms = set(required_perms)
208 225 self.user_perms = None
209 226
210 227 def __call__(self, func):
211 228 @wraps(func)
212 229 def _wrapper(*fargs, **fkwargs):
213 230 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
214 231 log.debug('checking %s permissions %s for %s',
215 232 self.__class__.__name__, self.required_perms, func.__name__)
216 233
217 234 if self.check_permissions():
218 235 log.debug('Permission granted for %s', func.__name__)
219 236 return func(*fargs)
220 237
221 238 else:
222 239 log.warning('Permission denied for %s', func.__name__)
223 240 #redirect with forbidden ret code
224 241 return abort(403)
225 242 return _wrapper
226 243
227 244
228 245 def check_permissions(self):
229 246 """
230 247 Dummy function for overriding
231 248 """
232 249 raise Exception('You have to write this function in child class')
233 250
234 251 class HasPermissionAllDecorator(PermsDecorator):
235 252 """
236 253 Checks for access permission for all given predicates. All of them have to
237 254 be meet in order to fulfill the request
238 255 """
239 256
240 257 def check_permissions(self):
241 258 if self.required_perms.issubset(self.user_perms.get('global')):
242 259 return True
243 260 return False
244 261
245 262
246 263 class HasPermissionAnyDecorator(PermsDecorator):
247 264 """
248 265 Checks for access permission for any of given predicates. In order to
249 266 fulfill the request any of predicates must be meet
250 267 """
251 268
252 269 def check_permissions(self):
253 270 if self.required_perms.intersection(self.user_perms.get('global')):
254 271 return True
255 272 return False
256 273
257 274 class HasRepoPermissionAllDecorator(PermsDecorator):
258 275 """
259 276 Checks for access permission for all given predicates for specific
260 277 repository. All of them have to be meet in order to fulfill the request
261 278 """
262 279
263 280 def check_permissions(self):
264 281 repo_name = get_repo_slug(request)
265 282 try:
266 283 user_perms = set([self.user_perms['repositories'][repo_name]])
267 284 except KeyError:
268 285 return False
269 286 if self.required_perms.issubset(user_perms):
270 287 return True
271 288 return False
272 289
273 290
274 291 class HasRepoPermissionAnyDecorator(PermsDecorator):
275 292 """
276 293 Checks for access permission for any of given predicates for specific
277 294 repository. In order to fulfill the request any of predicates must be meet
278 295 """
279 296
280 297 def check_permissions(self):
281 298 repo_name = get_repo_slug(request)
282 299
283 300 try:
284 301 user_perms = set([self.user_perms['repositories'][repo_name]])
285 302 except KeyError:
286 303 return False
287 304 if self.required_perms.intersection(user_perms):
288 305 return True
289 306 return False
290 307 #===============================================================================
291 308 # CHECK FUNCTIONS
292 309 #===============================================================================
293 310
294 311 class PermsFunction(object):
295 312 """
296 313 Base function for other check functions
297 314 """
298 315
299 316 def __init__(self, *perms):
300 317 available_perms = config['available_permissions']
301 318
302 319 for perm in perms:
303 320 if perm not in available_perms:
304 321 raise Exception("'%s' permission in not defined" % perm)
305 322 self.required_perms = set(perms)
306 323 self.user_perms = None
307 324 self.granted_for = ''
308 325 self.repo_name = None
309 326
310 327 def __call__(self, check_Location=''):
311 328 user = session.get('hg_app_user', False)
312 329 if not user:
313 330 return False
314 331 self.user_perms = user.permissions
315 332 self.granted_for = user.username
316 333 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
317 334
318 335 if self.check_permissions():
319 336 log.debug('Permission granted for %s @%s', self.granted_for,
320 337 check_Location)
321 338 return True
322 339
323 340 else:
324 341 log.warning('Permission denied for %s @%s', self.granted_for,
325 342 check_Location)
326 343 return False
327 344
328 345 def check_permissions(self):
329 346 """
330 347 Dummy function for overriding
331 348 """
332 349 raise Exception('You have to write this function in child class')
333 350
334 351 class HasPermissionAll(PermsFunction):
335 352 def check_permissions(self):
336 353 if self.required_perms.issubset(self.user_perms.get('global')):
337 354 return True
338 355 return False
339 356
340 357 class HasPermissionAny(PermsFunction):
341 358 def check_permissions(self):
342 359 if self.required_perms.intersection(self.user_perms.get('global')):
343 360 return True
344 361 return False
345 362
346 363 class HasRepoPermissionAll(PermsFunction):
347 364
348 365 def __call__(self, repo_name=None, check_Location=''):
349 366 self.repo_name = repo_name
350 367 return super(HasRepoPermissionAll, self).__call__(check_Location)
351 368
352 369 def check_permissions(self):
353 370 if not self.repo_name:
354 371 self.repo_name = get_repo_slug(request)
355 372
356 373 try:
357 374 self.user_perms = set([self.user_perms['repositories']\
358 375 [self.repo_name]])
359 376 except KeyError:
360 377 return False
361 378 self.granted_for = self.repo_name
362 379 if self.required_perms.issubset(self.user_perms):
363 380 return True
364 381 return False
365 382
366 383 class HasRepoPermissionAny(PermsFunction):
367 384
368 385
369 386 def __call__(self, repo_name=None, check_Location=''):
370 387 self.repo_name = repo_name
371 388 return super(HasRepoPermissionAny, self).__call__(check_Location)
372 389
373 390 def check_permissions(self):
374 391 if not self.repo_name:
375 392 self.repo_name = get_repo_slug(request)
376 393
377 394 try:
378 395 self.user_perms = set([self.user_perms['repositories']\
379 396 [self.repo_name]])
380 397 except KeyError:
381 398 return False
382 399 self.granted_for = self.repo_name
383 400 if self.required_perms.intersection(self.user_perms):
384 401 return True
385 402 return False
386 403
387 404 #===============================================================================
388 405 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
389 406 #===============================================================================
390 407
391 408 class HasPermissionAnyMiddleware(object):
392 409 def __init__(self, *perms):
393 410 self.required_perms = set(perms)
394 411
395 412 def __call__(self, user, repo_name):
396 413 usr = AuthUser()
397 414 usr.user_id = user.user_id
398 415 usr.username = user.username
399 416 usr.is_admin = user.admin
400 417
401 418 try:
402 419 self.user_perms = set([fill_perms(usr)\
403 420 .permissions['repositories'][repo_name]])
404 421 except:
405 422 self.user_perms = set()
406 423 self.granted_for = ''
407 424 self.username = user.username
408 425 self.repo_name = repo_name
409 426 return self.check_permissions()
410 427
411 428 def check_permissions(self):
412 429 log.debug('checking mercurial protocol '
413 430 'permissions for user:%s repository:%s',
414 431 self.username, self.repo_name)
415 432 if self.required_perms.intersection(self.user_perms):
416 433 log.debug('permission granted')
417 434 return True
418 435 log.debug('permission denied')
419 436 return False
@@ -1,192 +1,193 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # database managment for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 10, 2010
23 23 database managment and creation for hg app
24 24 @author: marcink
25 25 """
26 26
27 27 from os.path import dirname as dn, join as jn
28 28 import os
29 29 import sys
30 30 import uuid
31 31 ROOT = dn(dn(dn(os.path.realpath(__file__))))
32 32 sys.path.append(ROOT)
33 33
34 34 from pylons_app.lib.auth import get_crypt_password
35 35 from pylons_app.lib.utils import ask_ok
36 36 from pylons_app.model import init_model
37 37 from pylons_app.model.db import User, Permission, HgAppUi, HgAppSettings
38 38 from pylons_app.model import meta
39 39 from sqlalchemy.engine import create_engine
40 40 import logging
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 class DbManage(object):
45 45 def __init__(self, log_sql):
46 46 self.dbname = 'hg_app.db'
47 47 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
48 48 engine = create_engine(dburi, echo=log_sql)
49 49 init_model(engine)
50 50 self.sa = meta.Session
51 51 self.db_exists = False
52 52
53 53 def check_for_db(self, override):
54 54 log.info('checking for exisiting db')
55 55 if os.path.isfile(jn(ROOT, self.dbname)):
56 56 self.db_exists = True
57 57 log.info('database exisist')
58 58 if not override:
59 59 raise Exception('database already exists')
60 60
61 61 def create_tables(self, override=False):
62 62 """
63 63 Create a auth database
64 64 """
65 65 self.check_for_db(override)
66 66 if override:
67 67 log.info("database exisist and it's going to be destroyed")
68 68 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
69 69 if not destroy:
70 70 sys.exit()
71 71 if self.db_exists and destroy:
72 72 os.remove(jn(ROOT, self.dbname))
73 73 checkfirst = not override
74 74 meta.Base.metadata.create_all(checkfirst=checkfirst)
75 75 log.info('Created tables for %s', self.dbname)
76 76
77 77 def admin_prompt(self):
78 78 import getpass
79 79 username = raw_input('Specify admin username:')
80 80 password = getpass.getpass('Specify admin password:')
81 81 self.create_user(username, password, True)
82 82
83 83 def config_prompt(self):
84 84 log.info('Setting up repositories config')
85 85
86 86 path = raw_input('Specify valid full path to your repositories'
87 87 ' you can change this later in application settings:')
88 88
89 89 if not os.path.isdir(path):
90 90 log.error('You entered wrong path')
91 91 sys.exit()
92 92
93 93 hooks = HgAppUi()
94 94 hooks.ui_section = 'hooks'
95 95 hooks.ui_key = 'changegroup'
96 96 hooks.ui_value = 'hg update >&2'
97 97
98 98 web1 = HgAppUi()
99 99 web1.ui_section = 'web'
100 100 web1.ui_key = 'push_ssl'
101 101 web1.ui_value = 'false'
102 102
103 103 web2 = HgAppUi()
104 104 web2.ui_section = 'web'
105 105 web2.ui_key = 'allow_archive'
106 106 web2.ui_value = 'gz zip bz2'
107 107
108 108 web3 = HgAppUi()
109 109 web3.ui_section = 'web'
110 110 web3.ui_key = 'allow_push'
111 111 web3.ui_value = '*'
112 112
113 113 web4 = HgAppUi()
114 114 web4.ui_section = 'web'
115 115 web4.ui_key = 'baseurl'
116 116 web4.ui_value = '/'
117 117
118 118 paths = HgAppUi()
119 119 paths.ui_section = 'paths'
120 120 paths.ui_key = '/'
121 121 paths.ui_value = os.path.join(path, '*')
122 122
123 123
124 124 hgsettings = HgAppSettings()
125 125 hgsettings.app_auth_realm = 'hg-app authentication'
126 126 hgsettings.app_title = 'hg-app'
127 127
128 128 try:
129 129 #self.sa.add(hooks)
130 130 self.sa.add(web1)
131 131 self.sa.add(web2)
132 132 self.sa.add(web3)
133 133 self.sa.add(web4)
134 134 self.sa.add(paths)
135 135 self.sa.add(hgsettings)
136 136 self.sa.commit()
137 137 except:
138 138 self.sa.rollback()
139 139 raise
140 140 log.info('created ui config')
141 141
142 142 def create_user(self, username, password, admin=False):
143 143
144 144 log.info('creating default user')
145 145 #create default user for handling default permissions.
146 146 def_user = User()
147 147 def_user.username = 'default'
148 148 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
149 149 def_user.name = 'default'
150 150 def_user.lastname = 'default'
151 151 def_user.email = 'default@default.com'
152 152 def_user.admin = False
153 153 def_user.active = False
154 154
155 155 log.info('creating administrator user %s', username)
156 156 new_user = User()
157 157 new_user.username = username
158 158 new_user.password = get_crypt_password(password)
159 159 new_user.name = 'Hg'
160 160 new_user.lastname = 'Admin'
161 161 new_user.email = 'admin@localhost'
162 162 new_user.admin = admin
163 163 new_user.active = True
164 164
165 165 try:
166 166 self.sa.add(def_user)
167 167 self.sa.add(new_user)
168 168 self.sa.commit()
169 169 except:
170 170 self.sa.rollback()
171 171 raise
172 172
173 173 def create_permissions(self):
174 174 #module.(access|create|change|delete)_[name]
175 175 #module.(read|write|owner)
176 176 perms = [('repository.none', 'Repository no access'),
177 177 ('repository.read', 'Repository read access'),
178 178 ('repository.write', 'Repository write access'),
179 179 ('repository.admin', 'Repository admin access'),
180 ('repository.create', 'Repository create'),
180 181 ('hg.admin', 'Hg Administrator'),
181 182 ]
182 183
183 184 for p in perms:
184 185 new_perm = Permission()
185 186 new_perm.permission_name = p[0]
186 187 new_perm.permission_longname = p[1]
187 188 try:
188 189 self.sa.add(new_perm)
189 190 self.sa.commit()
190 191 except:
191 192 self.sa.rollback()
192 193 raise
@@ -1,105 +1,126 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Model for users
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 9, 2010
23 23 Model for users
24 24 @author: marcink
25 25 """
26 26
27 27 from pylons_app.model.db import User
28 28 from pylons_app.model.meta import Session
29 29 from pylons.i18n.translation import _
30 30 import logging
31 31 log = logging.getLogger(__name__)
32 32
33 33 class DefaultUserException(Exception):pass
34 34
35 35 class UserModel(object):
36 36
37 37 def __init__(self):
38 38 self.sa = Session()
39 39
40 40 def get_user(self, id):
41 41 return self.sa.query(User).get(id)
42 42
43 43 def create(self, form_data):
44 44 try:
45 45 new_user = User()
46 46 for k, v in form_data.items():
47 47 setattr(new_user, k, v)
48 48
49 49 self.sa.add(new_user)
50 50 self.sa.commit()
51 51 except Exception as e:
52 52 log.error(e)
53 53 self.sa.rollback()
54 54 raise
55 55
56 56 def create_registration(self, form_data):
57 57 try:
58 58 new_user = User()
59 59 for k, v in form_data.items():
60 60 if k != 'admin' or k != 'active':
61 61 setattr(new_user, k, v)
62 62 setattr(new_user, 'active', True)
63 63
64 64 self.sa.add(new_user)
65 65 self.sa.commit()
66 66 except Exception as e:
67 67 log.error(e)
68 68 self.sa.rollback()
69 69 raise
70 70
71 def update(self, id, form_data):
71 def update(self, uid, form_data):
72 72 try:
73 new_user = self.sa.query(User).get(id)
73 new_user = self.sa.query(User).get(uid)
74 74 if new_user.username == 'default':
75 75 raise DefaultUserException(
76 76 _("You can't Edit this user since it's"
77 77 " crucial for entire application"))
78 78 for k, v in form_data.items():
79 79 if k == 'new_password' and v != '':
80 80 new_user.password = v
81 81 else:
82 82 setattr(new_user, k, v)
83 83
84 84 self.sa.add(new_user)
85 85 self.sa.commit()
86 86 except Exception as e:
87 87 log.error(e)
88 88 self.sa.rollback()
89 89 raise
90 90
91 def update_my_account(self, uid, form_data):
92 try:
93 new_user = self.sa.query(User).get(uid)
94 if new_user.username == 'default':
95 raise DefaultUserException(
96 _("You can't Edit this user since it's"
97 " crucial for entire application"))
98 for k, v in form_data.items():
99 if k == 'new_password' and v != '':
100 new_user.password = v
101 else:
102 if k not in ['admin', 'active']:
103 setattr(new_user, k, v)
104
105 self.sa.add(new_user)
106 self.sa.commit()
107 except Exception as e:
108 log.error(e)
109 self.sa.rollback()
110 raise
111
91 112 def delete(self, id):
92 113
93 114 try:
94 115
95 116 user = self.sa.query(User).get(id)
96 117 if user.username == 'default':
97 118 raise DefaultUserException(
98 119 _("You can't remove this user since it's"
99 120 " crucial for entire application"))
100 121 self.sa.delete(user)
101 122 self.sa.commit()
102 123 except Exception as e:
103 124 log.error(e)
104 125 self.sa.rollback()
105 126 raise
@@ -1,84 +1,84 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')}
6 6 </%def>
7 7
8 8
9 9
10 10 <%def name="breadcrumbs_links()">
11 11 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24 <!-- end box / title -->
25 25
26 ${h.form(url('admin_setting', id='mapping'),method='put')}
26 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <h3>${_('Remap and rescan repositories')}</h3>
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label label-checkbox">
33 33 <label for="-button">${_('rescan option')}:</label>
34 34 </div>
35 35 <div class="checkboxes">
36 36 <div class="checkbox">
37 37 ${h.checkbox('destroy',True)}
38 38 <label for="checkbox-1">
39 39 <span class="tooltip" tooltip_title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
40 40 ${_('destroy old data')}</span> </label>
41 41 </div>
42 42 </div>
43 43 </div>
44 44
45 45 <div class="buttons">
46 46 ${h.submit('rescan','rescan repositories',class_="ui-button ui-widget ui-state-default ui-corner-all")}</td>
47 47 </div>
48 48 </div>
49 49 </div>
50 50 ${h.end_form()}
51 51
52 ${h.form(url('admin_setting', id='global'),method='put')}
52 ${h.form(url('admin_setting', setting_id='global'),method='put')}
53 53 <div class="form">
54 54 <!-- fields -->
55 55 <h3>${_('Global application settings')}</h3>
56 56 <div class="fields">
57 57
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="input-small">${_('Application name')}:</label>
61 61 </div>
62 62 <div class="input">
63 63 ${h.text('app_title',size=30)}
64 64 </div>
65 65 </div>
66 66
67 67 <div class="field">
68 68 <div class="label">
69 69 <label for="input-small">${_('Realm text')}:</label>
70 70 </div>
71 71 <div class="input">
72 72 ${h.text('app_auth_realm',size=30)}
73 73 </div>
74 74 </div>
75 75
76 76 <div class="buttons">
77 77 ${h.submit('save','save settings',class_="ui-button ui-widget ui-state-default ui-corner-all")}
78 78 </div>
79 79 </div>
80 80 </div>
81 81 ${h.end_form()}
82 82
83 83 </div>
84 84 </%def>
@@ -1,242 +1,242 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 4 <head>
5 5 <title>${next.title()}</title>
6 6 <link rel="icon" href="/images/hgicon.png" type="image/png" />
7 7 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
8 8 <meta name="robots" content="index, nofollow"/>
9 9 <!-- stylesheets -->
10 10 ${self.css()}
11 11 <!-- scripts -->
12 12 ${self.js()}
13 13 </head>
14 14 <body>
15 15 <!-- header -->
16 16 <div id="header">
17 17 <!-- user -->
18 18 <ul id="logged-user">
19 19 <li class="first">
20 ${h.link_to('%s %s (%s)'%(c.hg_app_user.name,c.hg_app_user.lastname,c.hg_app_user.username),h.url('edit_user', id=c.hg_app_user.user_id))}
20 ${h.link_to('%s %s (%s)'%(c.hg_app_user.name,c.hg_app_user.lastname,c.hg_app_user.username),h.url('admin_settings_my_account'))}
21 21 </li>
22 22 <li class="last highlight">${h.link_to(u'Logout',h.url('logout_home'))}</li>
23 23 </ul>
24 24 <!-- end user -->
25 25 <div id="header-inner">
26 26 <div id="home">
27 27 <a href="${h.url('hg_home')}"></a>
28 28 </div>
29 29 <!-- logo -->
30 30 <div id="logo">
31 31 <h1><a href="${h.url('hg_home')}">${c.hg_app_name}</a></h1>
32 32 </div>
33 33 <!-- end logo -->
34 34 <!-- quick menu -->
35 35 ${self.page_nav()}
36 36 <!-- end quick -->
37 37 <div class="corner tl"></div>
38 38 <div class="corner tr"></div>
39 39 </div>
40 40 </div>
41 41 <!-- end header -->
42 42
43 43 <!-- CONTENT -->
44 44 <div id="content">
45 45 <div class="flash_msg">
46 46 <% messages = h.flash.pop_messages() %>
47 47 % if messages:
48 48 <ul id="flash-messages">
49 49 % for message in messages:
50 50 <li class="${message.category}_msg">${message}</li>
51 51 % endfor
52 52 </ul>
53 53 % endif
54 54 </div>
55 55 <div id="main">
56 56 ${next.main()}
57 57 </div>
58 58 </div>
59 59 <!-- END CONTENT -->
60 60
61 61 <!-- footer -->
62 62 <div id="footer">
63 63 <p>Hg App ${c.hg_app_version} &copy; 2010 by Marcin Kuzminski</p>
64 64 <script type="text/javascript">${h.tooltip.activate()}</script>
65 65 </div>
66 66 <!-- end footer -->
67 67 </body>
68 68
69 69 </html>
70 70
71 71 ### MAKO DEFS ###
72 72 <%def name="page_nav()">
73 73 ${self.menu()}
74 74 </%def>
75 75
76 76 <%def name="menu(current=None)">
77 77 <%
78 78 def is_current(selected):
79 79 if selected == current:
80 80 return "class='current'"
81 81 %>
82 82 %if current not in ['home','admin']:
83 83 <script type="text/javascript">
84 84 YAHOO.util.Event.onDOMReady(function(){
85 85 YAHOO.util.Event.addListener('repo_switcher','click',function(){
86 86 if(YAHOO.util.Dom.hasClass('repo_switcher','selected')){
87 87 YAHOO.util.Dom.setStyle('switch_repos','display','none');
88 88 YAHOO.util.Dom.setStyle('repo_switcher','background','');
89 89 YAHOO.util.Dom.removeClass('repo_switcher','selected');
90 90 YAHOO.util.Dom.get('repo_switcher').removeAttribute('style');
91 91 }
92 92 else{
93 93 YAHOO.util.Dom.setStyle('switch_repos','display','');
94 94 //YAHOO.util.Dom.setStyle('repo_switcher','background','#FFFFFF');
95 95 //YAHOO.util.Dom.setStyle('repo_switcher','color','#556CB5');
96 96 YAHOO.util.Dom.addClass('repo_switcher','selected');
97 97 }
98 98 });
99 99 YAHOO.util.Event.addListener('repos_list','change',function(e){
100 100 var wa = YAHOO.util.Dom.get('repos_list').value;
101 101
102 102 var url = "${h.url('summary_home',repo_name='__REPLACE__')}".replace('__REPLACE__',wa);
103 103 window.location = url;
104 104 })
105 105 });
106 106 </script>
107 107
108 108 ##REGULAR MENU
109 109 <ul id="quick">
110 110 <!-- repo switcher -->
111 111 <li>
112 112 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
113 113 <span class="icon">
114 114 <img src="/images/icons/database.png" alt="${_('Products')}" />
115 115 </span>
116 116 <span>&darr;</span>
117 117 </a>
118 118 <div id="switch_repos" style="display:none;position: absolute;height: 25px;z-index: 1">
119 119 <select id="repos_list" size="=10" style="min-width: 150px">
120 120 %for repo in sorted(x.name.lower() for x in c.cached_repo_list.values()):
121 121 <option value="${repo}">${repo}</option>
122 122 %endfor
123 123 </select>
124 124 </div>
125 125 </li>
126 126
127 127 <li ${is_current('summary')}>
128 128 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
129 129 <span class="icon">
130 130 <img src="/images/icons/clipboard_16.png" alt="${_('Summary')}" />
131 131 </span>
132 132 <span>${_('Summary')}</span>
133 133 </a>
134 134 </li>
135 135 <li ${is_current('shortlog')}>
136 136 <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
137 137 <span class="icon">
138 138 <img src="/images/icons/application_double.png" alt="${_('Shortlog')}" />
139 139 </span>
140 140 <span>${_('Shortlog')}</span>
141 141 </a>
142 142 </li>
143 143 <li ${is_current('changelog')}>
144 144 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
145 145 <span class="icon">
146 146 <img src="/images/icons/time.png" alt="${_('Changelog')}" />
147 147 </span>
148 148 <span>${_('Changelog')}</span>
149 149 </a>
150 150 </li>
151 151 <li ${is_current('branches')}>
152 152 <a title="${_('Branches')}" href="${h.url('branches_home',repo_name=c.repo_name)}">
153 153 <span class="icon">
154 154 <img src="/images/icons/arrow_branch.png" alt="${_('Branches')}" />
155 155 </span>
156 156 <span>${_('Branches')}</span>
157 157 </a>
158 158 </li>
159 159 <li ${is_current('tags')}>
160 160 <a title="${_('Tags')}" href="${h.url('tags_home',repo_name=c.repo_name)}">
161 161 <span class="icon">
162 162 <img src="/images/icons/tag_blue.png" alt="${_('Tags')}" />
163 163 </span>
164 164 <span>${_('Tags')}</span>
165 165 </a>
166 166 </li>
167 167 <li ${is_current('files')}>
168 168 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
169 169 <span class="icon">
170 170 <img src="/images/icons/file.png" alt="${_('Files')}" />
171 171 </span>
172 172 <span>${_('Files')}</span>
173 173 </a>
174 174 </li>
175 175 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
176 176 <li ${is_current('settings')}>
177 177 <a title="${_('Settings')}" href="${h.url('repo_settings_home',repo_name=c.repo_name)}">
178 178 <span class="icon">
179 179 <img src="/images/icons/cog_edit.png" alt="${_('Settings')}" />
180 180 </span>
181 181 <span>${_('Settings')}</span>
182 182 </a>
183 183 </li>
184 184 %endif
185 185 </ul>
186 186 %else:
187 187 ##ROOT MENU
188 188 <ul id="quick">
189 189 <li>
190 190 <a title="${_('Home')}" href="${h.url('hg_home')}">
191 191 <span class="icon">
192 192 <img src="/images/icons/home_16.png" alt="${_('Home')}" />
193 193 </span>
194 194 <span>${_('Home')}</span>
195 195 </a>
196 196 </li>
197 197
198 198 %if h.HasPermissionAll('hg.admin')('access admin main page'):
199 199 <li ${is_current('admin')}>
200 200 <a title="${_('Admin')}" href="${h.url('admin_home')}">
201 201 <span class="icon">
202 202 <img src="/images/icons/cog_edit.png" alt="${_('Admin')}" />
203 203 </span>
204 204 <span>${_('Admin')}</span>
205 205 </a>
206 206 <ul>
207 207 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
208 208 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
209 209 ##<li>${h.link_to(_('permissions'),h.url('permissions'),class_='permissions')}</li>
210 210 <li>${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
211 211 </ul>
212 212 </li>
213 213 %endif
214 214
215 215 </ul>
216 216 %endif
217 217 </%def>
218 218
219 219
220 220 <%def name="css()">
221 221 <link rel="stylesheet" type="text/css" href="/css/reset.css" />
222 222 <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen" />
223 223 <link rel="stylesheet" type="text/css" href="/css/style_full.css" />
224 224 <link id="color" rel="stylesheet" type="text/css" href="/css/colors/blue.css" />
225 225 <link rel="stylesheet" type="text/css" href="/css/pygments.css" />
226 226 <link rel="stylesheet" type="text/css" href="/css/diff.css" />
227 227 </%def>
228 228
229 229 <%def name="js()">
230 230 <script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
231 231 <!--[if IE]><script language="javascript" type="text/javascript" src="/js/excanvas.min.js"></script><![endif]-->
232 232 <script type="text/javascript" src="/js/yui/container/container-min.js"></script>
233 233 <script type="text/javascript" src="/js/yui/datasource/datasource-min.js"></script>
234 234 <script type="text/javascript" src="/js/yui/autocomplete/autocomplete-min.js"></script>
235 235 <script type="text/javascript" src="/js/yui.flot.js"></script>
236 236 </%def>
237 237
238 238 <%def name="breadcrumbs()">
239 239 <div class="breadcrumbs">
240 240 ${self.breadcrumbs_links()}
241 241 </div>
242 242 </%def> No newline at end of file
@@ -1,77 +1,84 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/base.html"/>
3 3 <%def name="title()">
4 4 ${c.hg_app_name}
5 5 </%def>
6 6 <%def name="breadcrumbs()">
7 7 ${c.hg_app_name}
8 8 </%def>
9 9 <%def name="page_nav()">
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="main()">
13 13 <%def name="get_sort(name)">
14 14 <%name_slug = name.lower().replace(' ','_') %>
15 15 %if name_slug == c.cs_slug:
16 16 <span style="font-weight: bold;text-decoration: underline;">${name}</span>
17 17 %else:
18 18 <span style="font-weight: bold">${name}</span>
19 19 %endif
20 20 <a href="?sort=${name_slug}">&darr;</a>
21 21 <a href="?sort=-${name_slug}">&uarr;</a>
22 22 </%def>
23 23
24 24
25 25
26 26 <div class="box">
27 27 <!-- box / title -->
28 28 <div class="title">
29 29 <h5>${_('Dashboard')}</h5>
30 ##%if h.HasPermissionAll('repository.create')():
31 <ul class="links">
32 <li>
33 <span>${h.link_to(u'ADD NEW REPO',h.url('new_repo'),class_="add_icon")}</span>
34 </li>
35 </ul>
36 ##%endif
30 37 </div>
31 38 <!-- end box / title -->
32 39 <div class="table">
33 40 <table>
34 41 <thead>
35 42 <tr>
36 43 <th class="left">${get_sort(_('Name'))}</th>
37 44 <th class="left">${get_sort(_('Description'))}</th>
38 45 <th class="left">${get_sort(_('Last change'))}</th>
39 46 <th class="left">${get_sort(_('Tip'))}</th>
40 47 <th class="left">${get_sort(_('Contact'))}</th>
41 48 <th class="left">${_('RSS')}</th>
42 49 <th class="left">${_('Atom')}</th>
43 50 </tr>
44 51 </thead>
45 52 <tbody>
46 53 %for cnt,repo in enumerate(c.repos_list):
47 54 %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(repo['name'],'main page check'):
48 55 <tr class="parity${cnt%2}">
49 56 <td>
50 57 %if repo['repo'].dbrepo.private:
51 58 <img alt="${_('private')}" src="/images/icons/lock.png"/>
52 59 %else:
53 60 <img alt="${_('public')}" src="/images/icons/lock_open.png"/>
54 61 %endif
55 62 ${h.link_to(repo['name'],
56 63 h.url('summary_home',repo_name=repo['name']))}</td>
57 64 <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td>
58 65 <td>${h.age(repo['last_change'])}</td>
59 66 <td>${h.link_to_if(repo['rev']>=0,'r%s:%s' % (repo['rev'],repo['tip']),
60 67 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
61 68 class_="tooltip",
62 69 tooltip_title=h.tooltip(repo['last_msg']))}</td>
63 70 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
64 71 <td>
65 72 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
66 73 </td>
67 74 <td>
68 75 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
69 76 </td>
70 77 </tr>
71 78 %endif
72 79 %endfor
73 80 </tbody>
74 81 </table>
75 82 </div>
76 83 </div>
77 84 </%def>
General Comments 0
You need to be logged in to leave comments. Login now