##// END OF EJS Templates
added settings rest controllers for admin, updated routes with easier submodule handling
marcink -
r346:51362853 default
parent child Browse files
Show More
@@ -0,0 +1,93 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
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
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on July 14, 2010
22 settings controller for pylons
23 @author: marcink
24 """
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url
27 from pylons.controllers.util import abort, redirect
28 from pylons.i18n.translation import _
29 from pylons_app.lib import helpers as h
30 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator
31 from pylons_app.lib.base import BaseController, render
32 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.forms import UserForm
34 from pylons_app.model.user_model import UserModel
35 import formencode
36 import logging
37
38 log = logging.getLogger(__name__)
39
40
41 class SettingsController(BaseController):
42 """REST Controller styled on the Atom Publishing Protocol"""
43 # To properly map this controller, ensure your config/routing.py
44 # file has a resource setup:
45 # map.resource('setting', 'settings', controller='admin/settings',
46 # path_prefix='/admin', name_prefix='admin_')
47
48
49 @LoginRequired()
50 #@HasPermissionAllDecorator('hg.admin')
51 def __before__(self):
52 c.admin_user = session.get('admin_user')
53 c.admin_username = session.get('admin_username')
54 super(SettingsController, self).__before__()
55
56 def index(self, format='html'):
57 """GET /admin/settings: All items in the collection"""
58 # url('admin_settings')
59 return render('admin/settings/settings.html')
60
61 def create(self):
62 """POST /admin/settings: Create a new item"""
63 # url('admin_settings')
64
65 def new(self, format='html'):
66 """GET /admin/settings/new: Form to create a new item"""
67 # url('admin_new_setting')
68
69 def update(self, id):
70 """PUT /admin/settings/id: Update an existing item"""
71 # Forms posted to this method should contain a hidden field:
72 # <input type="hidden" name="_method" value="PUT" />
73 # Or using helpers:
74 # h.form(url('admin_setting', id=ID),
75 # method='put')
76 # url('admin_setting', id=ID)
77
78 def delete(self, id):
79 """DELETE /admin/settings/id: Delete an existing item"""
80 # Forms posted to this method should contain a hidden field:
81 # <input type="hidden" name="_method" value="DELETE" />
82 # Or using helpers:
83 # h.form(url('admin_setting', id=ID),
84 # method='delete')
85 # url('admin_setting', id=ID)
86
87 def show(self, id, format='html'):
88 """GET /admin/settings/id: Show a specific item"""
89 # url('admin_setting', id=ID)
90
91 def edit(self, id, format='html'):
92 """GET /admin/settings/id/edit: Form to edit an existing item"""
93 # url('admin_edit_setting', id=ID)
@@ -0,0 +1,21 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('Settings administration')}
6 </%def>
7 <%def name="breadcrumbs()">
8 ${h.link_to(u'Admin',h.url('admin_home'))}
9 /
10 ${_('Settings')}
11 </%def>
12 <%def name="page_nav()">
13 ${self.menu('admin')}
14 ${self.submenu('settings')}
15 </%def>
16 <%def name="main()">
17 <div>
18 <h2>${_('Settings administration')}</h2>
19
20 </div>
21 </%def>
@@ -0,0 +1,43 b''
1 from pylons_app.tests import *
2
3 class TestSettingsController(TestController):
4
5 def test_index(self):
6 response = self.app.get(url('admin_settings'))
7 # Test response...
8
9 def test_index_as_xml(self):
10 response = self.app.get(url('formatted_admin_settings', format='xml'))
11
12 def test_create(self):
13 response = self.app.post(url('admin_settings'))
14
15 def test_new(self):
16 response = self.app.get(url('admin_new_setting'))
17
18 def test_new_as_xml(self):
19 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
20
21 def test_update(self):
22 response = self.app.put(url('admin_setting', id=1))
23
24 def test_update_browser_fakeout(self):
25 response = self.app.post(url('admin_setting', id=1), params=dict(_method='put'))
26
27 def test_delete(self):
28 response = self.app.delete(url('admin_setting', id=1))
29
30 def test_delete_browser_fakeout(self):
31 response = self.app.post(url('admin_setting', id=1), params=dict(_method='delete'))
32
33 def test_show(self):
34 response = self.app.get(url('admin_setting', id=1))
35
36 def test_show_as_xml(self):
37 response = self.app.get(url('formatted_admin_setting', id=1, format='xml'))
38
39 def test_edit(self):
40 response = self.app.get(url('admin_edit_setting', id=1))
41
42 def test_edit_as_xml(self):
43 response = self.app.get(url('formatted_admin_edit_setting', id=1, format='xml'))
@@ -0,0 +1,43 b''
1 from pylons_app.tests import *
2
3 class TestSettingsHgController(TestController):
4
5 def test_index(self):
6 response = self.app.get(url('admin_settings_hg'))
7 # Test response...
8
9 def test_index_as_xml(self):
10 response = self.app.get(url('formatted_admin_settings_hg', format='xml'))
11
12 def test_create(self):
13 response = self.app.post(url('admin_settings_hg'))
14
15 def test_new(self):
16 response = self.app.get(url('admin_new_setting_hg'))
17
18 def test_new_as_xml(self):
19 response = self.app.get(url('formatted_admin_new_setting_hg', format='xml'))
20
21 def test_update(self):
22 response = self.app.put(url('admin_setting_hg', id=1))
23
24 def test_update_browser_fakeout(self):
25 response = self.app.post(url('admin_setting_hg', id=1), params=dict(_method='put'))
26
27 def test_delete(self):
28 response = self.app.delete(url('admin_setting_hg', id=1))
29
30 def test_delete_browser_fakeout(self):
31 response = self.app.post(url('admin_setting_hg', id=1), params=dict(_method='delete'))
32
33 def test_show(self):
34 response = self.app.get(url('admin_setting_hg', id=1))
35
36 def test_show_as_xml(self):
37 response = self.app.get(url('formatted_admin_setting_hg', id=1, format='xml'))
38
39 def test_edit(self):
40 response = self.app.get(url('admin_edit_setting_hg', id=1))
41
42 def test_edit_as_xml(self):
43 response = self.app.get(url('formatted_admin_edit_setting_hg', id=1, format='xml'))
@@ -1,128 +1,129 b''
1 1 """Routes configuration
2 2
3 3 The more specific and detailed routes should be defined first so they
4 4 may take precedent over the more generic routes. For more information
5 5 refer to the routes manual at http://routes.groovie.org/docs/
6 6 """
7 7 from routes import Mapper
8 8 from pylons_app.lib.utils import check_repo_fast as cr
9 9
10 10 def make_map(config):
11 11 """Create, configure and return the routes Mapper"""
12 12 map = Mapper(directory=config['pylons.paths']['controllers'],
13 13 always_scan=config['debug'])
14 14 map.minimization = False
15 15 map.explicit = False
16 16
17 17 # The ErrorController route (handles 404/500 error pages); it should
18 18 # likely stay at the top, ensuring it can always be resolved
19 19 map.connect('/error/{action}', controller='error')
20 20 map.connect('/error/{action}/{id}', controller='error')
21 21
22 22 # CUSTOM ROUTES HERE
23 23 map.connect('hg_home', '/', controller='hg', action='index')
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28 @param environ:
29 29 @param match_dict:
30 30 """
31 31 repo_name = match_dict.get('repo_name')
32 32 return not cr(repo_name, config['base_path'])
33 33
34 34 #REST routes
35 with map.submapper(path_prefix='/_admin', controller='pylons_app.controllers.admin.repos:ReposController') as m:
35 with map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
36 36 m.connect("repos", "/repos",
37 37 action="create", conditions=dict(method=["POST"]))
38 38 m.connect("repos", "/repos",
39 39 action="index", conditions=dict(method=["GET"]))
40 40 m.connect("formatted_repos", "/repos.{format}",
41 41 action="index",
42 42 conditions=dict(method=["GET"]))
43 43 m.connect("new_repo", "/repos/new",
44 44 action="new", conditions=dict(method=["GET"]))
45 45 m.connect("formatted_new_repo", "/repos/new.{format}",
46 46 action="new", conditions=dict(method=["GET"]))
47 47 m.connect("/repos/{repo_name:.*}",
48 48 action="update", conditions=dict(method=["PUT"],
49 49 function=check_repo))
50 50 m.connect("/repos/{repo_name:.*}",
51 51 action="delete", conditions=dict(method=["DELETE"],
52 52 function=check_repo))
53 53 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
54 54 action="edit", conditions=dict(method=["GET"],
55 55 function=check_repo))
56 56 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
57 57 action="edit", conditions=dict(method=["GET"],
58 58 function=check_repo))
59 59 m.connect("repo", "/repos/{repo_name:.*}",
60 60 action="show", conditions=dict(method=["GET"],
61 61 function=check_repo))
62 62 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
63 63 action="show", conditions=dict(method=["GET"],
64 64 function=check_repo))
65 65 #ajax delete repo perm user
66 66 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
67 67 action="delete_perm_user", conditions=dict(method=["DELETE"],
68 68 function=check_repo))
69 69
70 map.resource('user', 'users', controller='pylons_app.controllers.admin.users:UsersController', path_prefix='/_admin')
71 map.resource('permission', 'permissions', controller='pylons_app.controllers.admin.permissions:PermissionsController', path_prefix='/_admin')
70 map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
71 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
72 map.resource('setting', 'settings', controller='admin/settings', path_prefix='/_admin', name_prefix='admin_')
72 73
73 74 #ADMIN
74 with map.submapper(path_prefix='/_admin', controller='pylons_app.controllers.admin.admin:AdminController') as m:
75 with map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
75 76 m.connect('admin_home', '', action='index')#main page
76 77 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
77 78 action='add_repo')
78 79
79 80 #FEEDS
80 81 map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
81 82 controller='feed', action='rss',
82 83 conditions=dict(function=check_repo))
83 84 map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
84 85 controller='feed', action='atom',
85 86 conditions=dict(function=check_repo))
86 87
87 88 #LOGIN/LOGOUT
88 89 map.connect('login_home', '/login', controller='login')
89 90 map.connect('logout_home', '/logout', controller='login', action='logout')
90 91
91 92 #OTHERS
92 93 map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
93 94 controller='changeset', revision='tip',
94 95 conditions=dict(function=check_repo))
95 96 map.connect('summary_home', '/{repo_name:.*}/summary',
96 97 controller='summary', conditions=dict(function=check_repo))
97 98 map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
98 99 controller='shortlog', conditions=dict(function=check_repo))
99 100 map.connect('branches_home', '/{repo_name:.*}/branches',
100 101 controller='branches', conditions=dict(function=check_repo))
101 102 map.connect('tags_home', '/{repo_name:.*}/tags',
102 103 controller='tags', conditions=dict(function=check_repo))
103 104 map.connect('changelog_home', '/{repo_name:.*}/changelog',
104 105 controller='changelog', conditions=dict(function=check_repo))
105 106 map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
106 107 controller='files', revision='tip', f_path='',
107 108 conditions=dict(function=check_repo))
108 109 map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
109 110 controller='files', action='diff', revision='tip', f_path='',
110 111 conditions=dict(function=check_repo))
111 112 map.connect('files_raw_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
112 113 controller='files', action='rawfile', revision='tip', f_path='',
113 114 conditions=dict(function=check_repo))
114 115 map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
115 116 controller='files', action='annotate', revision='tip', f_path='',
116 117 conditions=dict(function=check_repo))
117 118 map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
118 119 controller='files', action='archivefile', revision='tip',
119 120 conditions=dict(function=check_repo))
120 121 map.connect('repo_settings_update', '/{repo_name:.*}/settings',
121 122 controller='settings', action="update",
122 123 conditions=dict(method=["PUT"], function=check_repo))
123 124 map.connect('repo_settings_home', '/{repo_name:.*}/settings',
124 125 controller='settings', action='index',
125 126 conditions=dict(function=check_repo))
126 127
127 128
128 129 return map
@@ -1,45 +1,45 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Users administration')}
6 6 </%def>
7 7 <%def name="breadcrumbs()">
8 8 ${h.link_to(u'Admin',h.url('admin_home'))}
9 9 /
10 10 ${_('Users')}
11 11 </%def>
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 ${self.submenu('users')}
15 15 </%def>
16 16 <%def name="main()">
17 17 <div>
18 <h2>${_('Mercurial users')}</h2>
18 <h2>${_('Users administration')}</h2>
19 19 <table class="table_disp">
20 20 <tr class="header">
21 21 <td>${_('username')}</td>
22 22 <td>${_('name')}</td>
23 23 <td>${_('lastname')}</td>
24 24 <td>${_('active')}</td>
25 25 <td>${_('admin')}</td>
26 26 <td>${_('action')}</td>
27 27 </tr>
28 28 %for user in c.users_list:
29 29 <tr>
30 30 <td>${h.link_to(user.username,h.url('edit_user', id=user.user_id))}</td>
31 31 <td>${user.name}</td>
32 32 <td>${user.lastname}</td>
33 33 <td>${user.active}</td>
34 34 <td>${user.admin}</td>
35 35 <td>
36 36 ${h.form(url('user', id=user.user_id),method='delete')}
37 37 ${h.submit('remove','delete',class_="delete_icon action_button")}
38 38 ${h.end_form()}
39 39 </td>
40 40 </tr>
41 41 %endfor
42 42 </table>
43 43 <span class="add_icon">${h.link_to(u'add user',h.url('new_user'))}</span>
44 44 </div>
45 45 </%def>
@@ -1,164 +1,164 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3 3 <html xmlns="http://www.w3.org/1999/xhtml" id="mainhtml">
4 4 <head>
5 5 <link rel="icon" href="/images/hgicon.png" type="image/png" />
6 6 <meta name="robots" content="index, nofollow"/>
7 7 <title>${next.title()}</title>
8 8 ##For future use yui reset for cross browser compatability.
9 9 ##<link rel="stylesheet" href="/js/yui/reset-fonts-grids/reset-fonts-grids.css" type="text/css" />
10 10 ${self.css()}
11 11 ${self.js()}
12 12 </head>
13 13
14 14 <body class="mainbody">
15 15 <div id="container">
16 16 <div class="page-header">
17 17 <h1 class="breadcrumbs">${next.breadcrumbs()}</h1>
18 18 ${self.page_nav()}
19 19 <div class="flash_msg">
20 20 <% messages = h.flash.pop_messages() %>
21 21 % if messages:
22 22 <ul id="flash-messages">
23 23 % for message in messages:
24 24 <li class="${message.category}_msg">${message}</li>
25 25 % endfor
26 26 </ul>
27 27 % endif
28 28 </div>
29 29 <div id="main">
30 30 ${next.main()}
31 31 <script type="text/javascript">${h.tooltip.activate()}</script>
32 32 </div>
33 33 <div class="page-footer">
34 34 Hg App ${c.hg_app_version} &copy; 2010 by Marcin Kuzminski
35 35 </div>
36 36
37 37 <div id="powered-by">
38 38 <p>
39 39 <a href="http://mercurial.selenic.com/" title="Mercurial">
40 40 <img src="/images/hglogo.png" width="75" height="90" alt="mercurial"/></a>
41 41 </p>
42 42 </div>
43 43
44 44 <div id="corner-top-left"></div>
45 45 <div id="corner-top-right"></div>
46 46 <div id="corner-bottom-left"></div>
47 47 <div id="corner-bottom-right"></div>
48 48
49 49 </div>
50 50 </body>
51 51 </html>
52 52
53 53 ### MAKO DEFS ###
54 54
55 55 <%def name="page_nav()">
56 56 ${self.menu()}
57 57 ${self.submenu()}
58 58 </%def>
59 59
60 60 <%def name="menu(current)">
61 61 <%
62 62 def is_current(selected):
63 63 if selected == current:
64 64 return "class='current'"
65 65 %>
66 66 %if current not in ['home','admin']:
67 67 ##regular menu
68 68 <script type="text/javascript">
69 69 YAHOO.util.Event.onDOMReady(function(){
70 70 YAHOO.util.Event.addListener('repo_switcher','click',function(){
71 71 if(YAHOO.util.Dom.hasClass('repo_switcher','selected')){
72 72 YAHOO.util.Dom.setStyle('switch_repos','display','none');
73 73 YAHOO.util.Dom.setStyle('repo_switcher','background','');
74 74 YAHOO.util.Dom.removeClass('repo_switcher','selected');
75 75 YAHOO.util.Dom.get('repo_switcher').removeAttribute('style');
76 76 }
77 77 else{
78 78 YAHOO.util.Dom.setStyle('switch_repos','display','');
79 79 YAHOO.util.Dom.setStyle('repo_switcher','background','#FFFFFF');
80 80 YAHOO.util.Dom.setStyle('repo_switcher','color','#556CB5');
81 81 YAHOO.util.Dom.addClass('repo_switcher','selected');
82 82 }
83 83 });
84 84 YAHOO.util.Event.addListener('repos_list','change',function(e){
85 85 var wa = YAHOO.util.Dom.get('repos_list').value;
86 86
87 87 var url = "${h.url('summary_home',repo_name='__REPLACE__')}".replace('__REPLACE__',wa);
88 88 window.location = url;
89 89 })
90 90 });
91 91 </script>
92 92 <ul class="page-nav">
93 93 <li>
94 94 <a id="repo_switcher" title="${_('Switch repository')}" href="#">&darr;</a>
95 95 <div id="switch_repos" style="display:none;position: absolute;height: 25px">
96 96 <select id="repos_list" size="=10" style="min-width: 150px">
97 97 %for repo in sorted(x.name.lower() for x in c.cached_repo_list.values()):
98 98 <option value="${repo}">${repo}</option>
99 99 %endfor
100 100 </select>
101 101 </div>
102 102 </li>
103 103 <li ${is_current('summary')}>${h.link_to(_('summary'),h.url('summary_home',repo_name=c.repo_name))}</li>
104 104 <li ${is_current('shortlog')}>${h.link_to(_('shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</li>
105 105 <li ${is_current('changelog')}>${h.link_to(_('changelog'),h.url('changelog_home',repo_name=c.repo_name))}</li>
106 106 <li ${is_current('branches')}>${h.link_to(_('branches'),h.url('branches_home',repo_name=c.repo_name))}</li>
107 107 <li ${is_current('tags')}>${h.link_to(_('tags'),h.url('tags_home',repo_name=c.repo_name))}</li>
108 108 <li ${is_current('files')}>${h.link_to(_('files'),h.url('files_home',repo_name=c.repo_name))}</li>
109 109 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
110 110 <li ${is_current('settings')}>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name))}</li>
111 111 %endif
112 112 </ul>
113 113 %else:
114 114 ##Root menu
115 115 <ul class="page-nav">
116 116 <li ${is_current('home')}>${h.link_to(_('Home'),h.url('/'))}</li>
117 117 %if h.HasPermissionAll('hg.admin')('access admin main page'):
118 118 <li ${is_current('admin')}>${h.link_to(_('Admin'),h.url('admin_home'))}</li>
119 119 %endif
120 120 <li class="logout">${h.link_to(u'Logout',h.url('logout_home'))}</li>
121 121 </ul>
122 122 %endif
123 123 </div>
124 124 </%def>
125 125 <%def name="submenu(current=None)">
126 126 <%
127 127 def is_current(selected):
128 128 if selected == current:
129 129 return "class='current_submenu'"
130 130 %>
131 131 %if current != None:
132 132 <div>
133 133 <ul class="submenu">
134 134 <li ${is_current('repos')}>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
135 135 <li ${is_current('users')}>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
136 136 ##commented<li ${is_current('permissions')}>${h.link_to(_('permissions'),h.url('permissions'),class_='permissions')}</li>
137 ##commented<li ${is_current('settings')}>${h.link_to(_('settings'),h.url('hgapp_settings'),class_='settings')}</li>
137 <li ${is_current('settings')}>${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
138 138 </ul>
139 139 </div>
140 140 %endif
141 141 </%def>
142 142
143 143
144 144 <%def name="css()">
145 145 <link rel="stylesheet" href="/css/monoblue_custom.css" type="text/css" />
146 146 </%def>
147 147
148 148 <%def name="js()">
149 149 <script type="text/javascript" src="/js/yui/utilities/utilities.js"></script>
150 150 <script type="text/javascript" src="/js/yui/container/container-min.js"></script>
151 151 <script type="text/javascript" src="/js/yui/datasource/datasource-min.js"></script>
152 152 <script type="text/javascript" src="/js/yui/autocomplete/autocomplete-min.js"></script>
153 153 </%def>
154 154
155 155 <!-- DEFINITION OF FORM ERROR FETCHER -->
156 156 <%def name="get_form_error(element)">
157 157 %if hasattr(c,'form_errors') and type(c.form_errors) == dict:
158 158 %if c.form_errors.get(element,False):
159 159 <span class="error-message">
160 160 ${c.form_errors.get(element,'')}
161 161 </span>
162 162 %endif
163 163 %endif
164 164 </%def> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now