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