Mailing List Archive

On-line admin
So I wanted a project to familiarize myself with the Trac code base and
I figured #11 (Web configuration/administration instead of trac-admin)
would be a good place to start ;-)

The attached patch is a first stab at exposing relevant functionality
from trac-admin via a new Admin module. The UI's not the greatest (and
not 100% consistent), there's very little error handling and it has a
couple of bugs:

- it only requires TRAC_ADMIN permission, and that's not fully enforced
due to a bug in the permissions code

- version and milestone times are being shifted on each update; probably
the rendering and parsing of times are applying the local timezone
differently, I haven't dug into it

- you can't delete multiple items; it's a UI design limitation for most
stuff, but for priorities, severities and permissions it's because I
haven't figured out a way to access all the values of a multi-valued
HTTP request parameter yet

- alot of code is copy/pasted from trac-admin; I need to go back and
refactor to extract a common, shared module if I carry on with this

So, not something 'ready to commit' but it's a start. I didn't want to
take it any further without some feedback. I achieved my basic goal of
getting some initial familiarity with the code base, so my question is:
should I take this further?

More specifically, Danial/Jonas/Rocky, are you interested in having
someone work on this? Would you place this above or below CVS
integration, which I'm also looking at (as Daniel already knows)?

If you'd be interested in seeing this go forward, I wouldn't mind
advice/suggestions on how the UI could improve, as well as code review
in case I'm doing anything horrible! :-)

Anyway, here it is:

L.

Index: trac/core.py
===================================================================
--- trac/core.py (revision 443)
+++ trac/core.py (working copy)
@@ -51,6 +51,7 @@
'changeset' : ('Changeset', 'Changeset', 1),
'newticket' : ('Ticket', 'Newticket', 0),
'attachment' : ('File', 'Attachment', 0),
+ 'admin' : ('Admin', 'Admin', 0),
}

def parse_path_info(path_info):
@@ -117,6 +118,12 @@
args['id'] = match.group(2)
args['filename'] = match.group(3)
return args
+ match = re.search('^/admin/([a-zA-Z_]+)?/?([a-zA-Z_]+)?', path_info)
+ if match:
+ args['mode'] = 'admin'
+ args['type'] = match.group(1)
+ args['user'] = match.group(2)
+ return args
return args

def parse_args(command, path_info, query_string,
@@ -199,6 +206,7 @@
hdf.setValue('project.name', env.get_config('project', 'name'))
hdf.setValue('project.descr', env.get_config('project', 'descr'))

+ hdf.setValue('trac.href.admin', env.href.admin())
hdf.setValue('trac.href.wiki', env.href.wiki())
hdf.setValue('trac.href.browser', env.href.browser('/'))
hdf.setValue('trac.href.timeline', env.href.timeline())
Index: trac/Admin.py
===================================================================
--- trac/Admin.py (revision 0)
+++ trac/Admin.py (revision 0)
@@ -0,0 +1,346 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2004 Edgewall Software
+# Copyright (C) 2004 Jonas Borgstrˆm <jonas@edgewall.com>
+# Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com>
+#
+# Trac is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# Trac is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# Author: Jonas Borgstrˆm <jonas@edgewall.com>
+
+import perm
+from Module import Module
+import neo_cgi
+import neo_cs
+import sqlite
+import time
+
+class Admin (Module):
+ template_name = 'admin.cs'
+
+ def render (self):
+ #self.perm.assert_permission(perm.TRAC_ADMIN) FIXME: fails to
find the permission?!
+ self.req.hdf.setValue('title', 'Admin')
+ type = self.args.get('type', 'components')
+ user = self.args.get('user')
+ if (type == None):
+ type='components'
+ action = self.get_action()
+
+ if action and action != 'view':
+ # Process requested updates
+ # TODO: validate inputs
+ handler = getattr(self, 'do_' + type)
+ handler(action)
+ self.req.redirect(self.env.href.admin(type, user))
+ else:
+ # Render the page
+ self.req.hdf.setValue('admin.type', type)
+ renderer = getattr(self, 'render_' + type)
+ renderer()
+
+ def get_action (self):
+ self.action = 'view'
+ if self.args.get('add'):
+ self.action = 'add'
+ elif self.args.get('update'):
+ self.action = 'update'
+ elif self.args.get('delete'):
+ self.action = 'delete'
+ return self.action
+
+
+ def do_components(self, action):
+ if action == 'add':
+ self.do_component_add()
+ elif action == 'update':
+ self.do_component_update()
+ elif action == 'delete':
+ self.do_component_delete()
+
+ def do_component_add(self):
+ name = self.args.get('name')
+ owner = self.args.get('owner')
+ self._db_execsql("INSERT INTO component VALUES('%s', '%s')"
+ % (name, owner))
+
+ def do_component_update(self):
+ name = self.args.get('name')
+ owner = self.args.get('owner')
+ newname = self.args.get('newname')
+ newowner = self.args.get('newowner')
+
+ if (name != newname) or (owner != newowner):
+ cnx = self.env.get_db_cnx()
+ cursor = cnx.cursor ()
+ if (owner != newowner):
+ self._db_execsql("UPDATE component SET owner='%s' WHERE
name='%s'"
+ % (newowner,name), cursor)
+ if (name != newname):
+ self._db_execsql("UPDATE component SET name='%s' WHERE
name='%s'"
+ % (newname,name), cursor)
+ self._db_execsql("UPDATE ticket SET component='%s'
WHERE component='%s'"
+ % (newname,name), cursor)
+ cnx.commit()
+
+ def do_component_delete(self):
+ name = self.args.get('name')
+ self._db_execsql("DELETE FROM component WHERE name='%s'"
+ % (name))
+
+
+ def do_versions(self, action):
+ if action == 'add':
+ self.do_mile_ver_add('version')
+ if action == 'update':
+ self.do_mile_ver_update('version')
+ if action == 'delete':
+ self.do_mile_ver_delete('version')
+
+ def do_milestones(self, action):
+ if action == 'add':
+ self.do_mile_ver_add('milestone')
+ if action == 'update':
+ self.do_mile_ver_update('milestone')
+ if action == 'delete':
+ self.do_mile_ver_delete('milestone')
+
+ def do_mile_ver_add(self, type):
+ name = self.args.get('name')
+ t = self.args.get('time')
+ seconds = self._parse_time(t)
+
+ self._db_execsql("INSERT INTO %(type)s('name','time') "
+ " VALUES('%(name)s', '%(time)s')"
+ % {'type':type, 'name':name, 'time':seconds})
+
+ def do_mile_ver_update(self, type):
+ name = self.args.get('name')
+ newname = self.args.get('newname')
+ t = self.args.get('newtime')
+ seconds = self._parse_time(t)
+
+ cnx = self.env.get_db_cnx()
+ cursor = cnx.cursor ()
+ self._db_execsql("UPDATE %(type)s SET name='%(newname)s'"
+ " WHERE name='%(name)s'"
+ % {'type':type, 'newname':newname,
'name':name},
+ cursor)
+ if seconds:
+ self._db_execsql("UPDATE %s SET time='%s'"
+ " WHERE name='%s'" % (type, seconds, newname),
+ cursor)
+ cnx.commit()
+
+ def do_mile_ver_delete(self, type):
+ name = self.args.get('name')
+ d = {'name':name, 'type':type}
+ data = self._db_execsql("SELECT name FROM %(type)s"
+ " WHERE name='%(name)s'" % d)
+ #if not data:
+ # raise Exception, "No such %s '%s'" % (type, name) FIXME:
clean error reporting
+ data = self._db_execsql("DELETE FROM %(type)s"
+ " WHERE name='%(name)s'" % d)
+
+
+ def do_priorities(self, action):
+ if action == 'add':
+ self.do_enum_add('priority')
+ if action == 'update':
+ self.do_enum_update('priority')
+ if action == 'delete':
+ self.do_enum_delete('priority')
+
+ def do_severities(self, action):
+ if action == 'add':
+ self.do_enum_add('severity')
+ if action == 'update':
+ self.do_enum_update('severity')
+ if action == 'delete':
+ self.do_enum_delete('severity')
+
+ def do_enum_add(self, type):
+ name = self.args.get('name')
+ sql = ("INSERT INTO enum('value','type','name') "
+ " SELECT 1+ifnull(max(value),0),'%(type)s','%(name)s'"
+ " FROM enum WHERE type='%(type)s'"
+ % {'type':type, 'name':name})
+ self._db_execsql(sql)
+ # FIXME enum already in response; have to do a refresh
+
+ def do_enum_update(self, type):
+ name = self.args.get('selection')
+ newname = self.args.get('name')
+ d = {'name':name, 'newname':newname, 'type':type}
+ data = self._db_execsql("SELECT name FROM enum"
+ " WHERE type='%(type)s' AND
name='%(name)s'" % d)
+ if not data:
+ raise Exception, "No such value '%s'" % name
+ data = self._db_execsql("UPDATE enum SET name='%(newname)s'"
+ " WHERE type='%(type)s' AND
name='%(name)s'" % d)
+
+ def do_enum_delete(self, type):
+ name = self.args.get('selection')
+ data = self._db_execsql("SELECT name FROM enum"
+ " WHERE type='%s' AND name='%s'" %
(type, name))
+ if not data:
+ raise Exception, "No such value '%s'" % name
+ data = self._db_execsql("DELETE FROM enum WHERE type='%s' AND
name='%s'"
+ % (type, name))
+
+
+ def do_permissions(self, action):
+ if action == 'add':
+ self.do_permission_add()
+ if action == 'delete':
+ self.do_permission_remove()
+
+ def do_permission_add(self):
+ user = self.args.get('user')
+ perms = self.args.get('permissions.avail')
+
+ #for action in perms:
+ action = perms
+ self._db_execsql("INSERT INTO permission VALUES('%s', '%s')" %
(user, action))
+
+
+ def do_permission_remove(self):
+ user = self.args.get('user')
+ perms = self.args.get('permissions.grant')
+
+ #for action in perms:
+ action = perms
+ self._db_execsql("DELETE FROM permission WHERE username='%s'
AND action='%s'" %
+ (user, action))
+
+
+ #
+ # Page rendering
+ #
+
+ def render_components (self):
+ idx = 0
+ components = self._db_execsql('SELECT name,owner FROM component
ORDER BY name')
+ for item in components:
+ self.req.hdf.setValue('admin.components.%d.name' % idx,
item[0])
+ self.req.hdf.setValue('admin.components.%d.owner' % idx,
item[1])
+ idx = idx + 1
+
+ def render_versions (self):
+ idx = 0
+ versions = self._db_execsql('SELECT name,time FROM version
ORDER BY time,name')
+ for item in versions:
+ secs = item[1]
+ st = time.gmtime(secs)
+ tm = time.strftime('%x %X', st)
+
+ self.req.hdf.setValue('admin.versions.%d.name' % idx, item[0])
+ self.req.hdf.setValue('admin.versions.%d.time' % idx, tm)
+
+ idx = idx + 1
+
+ def render_milestones (self):
+ idx = 0
+ milestones = self._db_execsql('SELECT name,time FROM milestone
ORDER BY time,name')
+ for item in milestones:
+ secs = item[1]
+ st = time.gmtime(secs)
+ tm = time.strftime('%x %X', st)
+
+ self.req.hdf.setValue('admin.milestones.%d.name' % idx,
item[0])
+ self.req.hdf.setValue('admin.milestones.%d.time' % idx, tm)
+ idx = idx + 1
+ self.req.hdf.setValue('admin.milestone.count', str(idx))
+
+ def render_priorities (self):
+ # data's already present, nothing to do
+ pass
+
+ def render_severities (self):
+ # data's already present, nothing to do
+ pass
+
+ def render_permissions(self):
+ user = self.args.get('user')
+ if user:
+ avail = perm.meta_permission.keys()
+ avail = avail + perm.base_permissions
+
+ data = self._db_execsql("SELECT action FROM permission "
+ "WHERE username='%s'"
+ "ORDER BY action" % user)
+
+ idx = 0
+ for row in data:
+ item = row[0]
+# if item in avail:
+# del avail[item]
+ self.req.hdf.setValue('admin.permissions.grant.%d.name'
% idx, item)
+ idx = idx + 1
+
+ idx = 0
+ avail.sort()
+ for item in avail:
+ self.req.hdf.setValue('admin.permissions.avail.%d.name'
%idx, item)
+ idx = idx + 1
+
+ self.req.hdf.setValue('admin.user', user)
+
+
+ #
+ # Utility methods
+ #
+
+ def _db_open(self):
+ try:
+ #if not self.__env:
+ # self.__env = trac.Environment.Environment (self.envname)
+ #return self.__env.get_db_cnx()
+ return self.env.get_db_cnx()
+ except Exception, e:
+ print 'Failed to open environment.', e
+ sys.exit(1)
+
+ def _db_execsql (self, sql, cursor=None):
+ data = []
+ if not cursor:
+ cnx = self._db_open()
+ cursor = cnx.cursor()
+ else:
+ cnx = None
+ cursor.execute(sql)
+ while 1:
+ row = cursor.fetchone()
+ if row == None:
+ break
+ data.append(row)
+ if cnx:
+ cnx.commit()
+ return data
+
+ def _parse_time(self, t):
+ seconds = None
+ if t == 'now':
+ seconds = str(int(time.time()))
+ else:
+ for format in ['%x %X', '%x, %X', '%X %x', '%X, %x', '%x']:
+ try:
+ seconds = str(time.mktime(time.strptime(t,
format))) #FIXME: time isn't round-tripping correctly
+ except ValueError:
+ continue
+
+ return seconds
+
+
\ No newline at end of file
Index: trac/perm.py
===================================================================
--- trac/perm.py (revision 443)
+++ trac/perm.py (working copy)
@@ -60,7 +60,17 @@
WIKI_ADMIN : [WIKI_VIEW, WIKI_CREATE, WIKI_MODIFY, WIKI_DELETE]
}

-
+base_permissions = [.
+ TIMELINE_VIEW, SEARCH_VIEW, CONFIG_VIEW,
+ LOG_VIEW, FILE_VIEW, CHANGESET_VIEW, BROWSER_VIEW,
+
+ TICKET_VIEW, TICKET_CREATE, TICKET_MODIFY,
+
+ REPORT_VIEW, REPORT_SQL_VIEW, REPORT_CREATE, REPORT_MODIFY,
REPORT_DELETE,
+
+ WIKI_VIEW, WIKI_CREATE, WIKI_MODIFY, WIKI_DELETE
+ ]
+
class PermissionError (StandardError):
"""Insufficient permissions to complete the operation"""
def __init__ (self, action):
Index: trac/Href.py
===================================================================
--- trac/Href.py (revision 443)
+++ trac/Href.py (working copy)
@@ -105,3 +105,10 @@
else:
return href_join(self.base, 'attachment', module, id,
filename)

+ def admin(self, type = None, user = None):
+ if type:
+ if user:
+ return href_join(self.base, 'admin', type, user)
+ return href_join(self.base, 'admin', type+'/')
+ return href_join(self.base, 'admin/')
+
\ No newline at end of file
Index: templates/macros.cs
===================================================================
--- templates/macros.cs (revision 443)
+++ templates/macros.cs (working copy)
@@ -21,4 +21,83 @@
if:!part.last ?><span class="browser-pathsep">/</span><?cs /if ?><?cs
/each ?><?cs if:file.filename ?><span class="filename"><?cs
var:file.filename
?></span><?cs /if ?></div>
-<?cs /def ?>
\ No newline at end of file
+<?cs /def ?>
+
+<?cs def:hdf_editlist(enum) ?>
+ <input type="text" name="name"><br />
+ <table>
+ <tr><td>
+ <select size="5" multiple="true" name="selection">
+ <?cs each:item = $enum ?>
+ <option><?cs var:item.name ?></option>
+ <?cs /each ?>
+ </select>
+ </td><td>
+ <input type="submit" name="add" value="Add" /><br />
+ <input type="submit" name="update" value="Rename" /><br />
+ <input type="submit" name="delete" value="Remove" /><br />
+ </td></tr>
+ </table>
+<?cs /def?>
+
+<!--
+<?cs def:hdf_selector(available, selected) ?>
+ <script>
+ // Move a selected field from one select list to another.
+ function moveField(fromSelect, toSelect)
+ {
+ var fromSelectedIndex = fromSelect.selectedIndex;
+ // Obtain a reference to the option being moved.
+ if( fromSelectedIndex != -1 )
+ {
+ var o = fromSelect.options[fromSelectedIndex];
+ var o2 = new Option(o.text,o.value,false,false);
+
+ // Remove option from available list.
+ fromSelect.options[fromSelectedIndex] = null;
+
+ // Add the option to the end of the to list.
+ toSelect.options[toSelect.options.length] = o2;
+ }
+ }
+
+ // Move an item from available field list to selected field list.
+ function toSelected() {
+ var availableSelect =
window.document.displaySettingForm.availableSelect;
+ var displayedSelect =
window.document.displaySettingForm.displayedSelect;
+ moveField(availableSelect, displayedSelect);
+ }
+
+ // Move an item from displayed field list to the available field list.
+ function toAvailable() {
+ var displayedSelect =
window.document.displaySettingForm.displayedSelect;
+ var availableSelect =
window.document.displaySettingForm.availableSelect;
+ moveField(displayedSelect, availableSelect);
+ }
+
+
+ </script>
+ <table>
+ <tr>
+ <td>
+ Available Permissions:
+ <select>
+ <?cs each:item = $available ?>
+ <option id="<?cs var:item.name ?>"><?cs var:item.name ?></option>
+ <?cs /each ?>
+ </select>
+ </td><td valign="center">
+ <input type="button" value="==&gt;" onclick="add_selected()" /><br />
+ <input type="button" value="&lt;==" onclick="remove_selected()"
/><br />
+ </td><td>
+ Granted Permissions:
+ <select>
+ <?cs each:item = $selected ?>
+ <option id="<?cs var:item.name ?>"><?cs var:item.name ?></option>
+ <?cs /each ?>
+ </select>
+ </td>
+ </tr>
+ </table>
+<?cs /def ?>
+-->
Index: templates/header.cs
===================================================================
--- templates/header.cs (revision 443)
+++ templates/header.cs (working copy)
@@ -33,6 +33,8 @@
@import url("<?cs var:$htdocs_location ?>/css/report.css");
<?cs elif:trac.active_module == 'search' ?>
@import url("<?cs var:$htdocs_location ?>/css/search.css");
+ <?cs elif:trac.active_module == 'admin' ?>
+ @import url("<?cs var:$htdocs_location ?>/css/admin.css");
<?cs /if ?>
/* Dynamically/template-generated CSS below */
#navbar { background: url("<?cs var:$htdocs_location
?>/topbar_gradient.png") top left #f7f7f7 }
@@ -109,5 +111,7 @@
"TICKET_CREATE", "9") ?>
<?cs call:navlink("Search", $trac.href.search, "search",
"SEARCH_VIEW", "4") ?>
+ <?cs call:navlink("Admin", $trac.href.admin, "admin",
+ "TRAC_ADMIN", "") ?>
</ul>
</div>
\ No newline at end of file
On-line admin [ In reply to ]
Laurie Harper wrote:

> So I wanted a project to familiarize myself with the Trac code base and
> I figured #11 (Web configuration/administration instead of trac-admin)
> would be a good place to start ;-)

Great, welcome aboard!
>
> The attached patch is a first stab at exposing relevant functionality
> from trac-admin via a new Admin module. The UI's not the greatest (and
> not 100% consistent), there's very little error handling and it has a
> couple of bugs:
>
> - it only requires TRAC_ADMIN permission, and that's not fully enforced
> due to a bug in the permissions code

I will commit a fix for this within the next few minutes. Just to make
it clear, the problem is was only possible to verify that users had
normal permissions like WIKI_VIEW and not "meta permission" like
TRAC_ADMIN or WIKI_ADMIN.
>
> - version and milestone times are being shifted on each update; probably
> the rendering and parsing of times are applying the local timezone
> differently, I haven't dug into it
>
yeah, probably a timezone issue, timestamps should be stored in UTC
format.

> - you can't delete multiple items; it's a UI design limitation for most
> stuff, but for priorities, severities and permissions it's because I
> haven't figured out a way to access all the values of a multi-valued
> HTTP request parameter yet
>
This is true, currently only the first value for each key is kept
in the self.args dictionary. This is bad. It's probably best to convert
the code to use the fieldstorage directly.

> - alot of code is copy/pasted from trac-admin; I need to go back and
> refactor to extract a common, shared module if I carry on with this

Yes, it wouldn't hurt if trac-admin and your Admin module both
used a common api. I'm not sure if Rocky already has started to look
at this?
>
> So, not something 'ready to commit' but it's a start. I didn't want to
> take it any further without some feedback. I achieved my basic goal of
> getting some initial familiarity with the code base, so my question is:
> should I take this further?
>
I've taken a quick look at the code and it looks right. It would
probably be a lot simpler/cleaner if a usable admin-api existed, but
that can be fixed later.

> More specifically, Danial/Jonas/Rocky, are you interested in having
> someone work on this? Would you place this above or below CVS
> integration, which I'm also looking at (as Daniel already knows)?
>
Yes, as you noticed an admin module has been on the todo list for
a long time (ticket #11). Compared to a CVS backend, an admin module
could probably be integrated with very limited changes to the rest of
the code base. Because of this I think that an admin module will
be integrated before any cvs support.

> If you'd be interested in seeing this go forward, I wouldn't mind
> advice/suggestions on how the UI could improve, as well as code review
> in case I'm doing anything horrible! :-)

A good start would be to attach admin.cs so we could take a look :)

/ Jonas
--
Jonas Borgström | Edgewall Software
jonas@edgewall.com | Professional GNU/Linux & Open Source Consulting.
| http://www.edgewall.com/
Re: On-line admin [ In reply to ]
Jonas Borgström wrote:
> Laurie Harper wrote:
>> So I wanted a project to familiarize myself with the Trac code base
>> and I figured #11 (Web configuration/administration instead of
>> trac-admin) would be a good place to start ;-)
> Great, welcome aboard!

Thank you :)

>> The attached patch is a first stab at exposing relevant functionality
>> from trac-admin via a new Admin module. The UI's not the greatest (and
>> not 100% consistent), there's very little error handling and it has a
>> couple of bugs:
>>
>> - it only requires TRAC_ADMIN permission, and that's not fully
>> enforced due to a bug in the permissions code
>
> I will commit a fix for this within the next few minutes. Just to make
> it clear, the problem is was only possible to verify that users had
> normal permissions like WIKI_VIEW and not "meta permission" like
> TRAC_ADMIN or WIKI_ADMIN.

Yep; it didn't look too serious. Is TRAC_ADMIN the right thing to
require though? I was thinking maybe it should be finer grained than that.

>> - version and milestone times are being shifted on each update;
>> probably the rendering and parsing of times are applying the local
>> timezone differently, I haven't dug into it
>>
> yeah, probably a timezone issue, timestamps should be stored in UTC
> format.

I think it's the way I'm parsing the date and the way I'm rendering it
applying timezone corrections differently, I'll have to do some digging.

>> - you can't delete multiple items; it's a UI design limitation for
>> most stuff, but for priorities, severities and permissions it's
>> because I haven't figured out a way to access all the values of a
>> multi-valued HTTP request parameter yet
>>
> This is true, currently only the first value for each key is kept
> in the self.args dictionary. This is bad. It's probably best to convert
> the code to use the fieldstorage directly.

I had a look at the Request/CGIRequest classes and I don't think it'll
be a problem to extend to support this; just adding a get_all() that
returns a list for multi-valued parameters should do the trick.

>> - alot of code is copy/pasted from trac-admin; I need to go back and
>> refactor to extract a common, shared module if I carry on with this
>
> Yes, it wouldn't hurt if trac-admin and your Admin module both
> used a common api. I'm not sure if Rocky already has started to look
> at this?

I started along these lines but decided 'one step at a time'; this is a
learning experience at the moment :-) But it's definately a priority to
get rid of the duplication going forward.

>> So, not something 'ready to commit' but it's a start. I didn't want to
>> take it any further without some feedback. I achieved my basic goal of
>> getting some initial familiarity with the code base, so my question
>> is: should I take this further?
>>
> I've taken a quick look at the code and it looks right. It would
> probably be a lot simpler/cleaner if a usable admin-api existed, but
> that can be fixed later.

Yep; that'll reduce the amount of code in Admin.py and trac-admin and
allow error detection to be shared by both. Definately a good idea.

>> More specifically, Danial/Jonas/Rocky, are you interested in having
>> someone work on this? Would you place this above or below CVS
>> integration, which I'm also looking at (as Daniel already knows)?
>>
> Yes, as you noticed an admin module has been on the todo list for
> a long time (ticket #11). Compared to a CVS backend, an admin module
> could probably be integrated with very limited changes to the rest of
> the code base. Because of this I think that an admin module will
> be integrated before any cvs support.

I don't think CVS support'll be that big an impact either. I started on
that last night and already have almost all the svn-specific code from
Browser.py and Log.py factored out into a seperate component that
provides an abstraction and an svn implementation. I'll start posting
patches so you can see what I'm up to in bite sized chunks :)

>> If you'd be interested in seeing this go forward, I wouldn't mind
>> advice/suggestions on how the UI could improve, as well as code review
>> in case I'm doing anything horrible! :-)
>
> A good start would be to attach admin.cs so we could take a look :)

Oops, I thought it was in the patch; must have missed an 'svn add' for
it; included below.

L.


Index: templates/admin.cs
===================================================================
--- templates/admin.cs (revision 0)
+++ templates/admin.cs (revision 0)
@@ -0,0 +1,211 @@
+<?cs include: "header.cs"?>
+<?cs include "macros.cs" ?>
+<div id="page-content">
+ <div class="subheader-links">
+ <li><a href="<?cs var:trac.href.admin ?>components/">Components</a></li>
+ <li><a href="<?cs var:trac.href.admin ?>versions/">Versions</a></li>
+ <li><a href="<?cs var:trac.href.admin ?>milestones/">Milestones</a></li>
+ <li><a href="<?cs var:trac.href.admin ?>priorities/">Priorities</a></li>
+ <li><a href="<?cs var:trac.href.admin ?>severities/">Severities</a></li>
+ <li><a href="<?cs var:trac.href.admin ?>permissions/">Permission</a></li>
+ </div>
+ <div id="main">
+ <div id="main-content">
+ <div id="browser-body">
+
+ <?cs if admin.type == "components" ?>
+
+ <h1>Components</h1>
+ <table>
+ <tr>
+ <td>Name</td>
+ <td>Owner</td>
+ </tr>
+ <tr>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>">
+ <td><input type="text" name="name" /></td>
+ <td><input type="text" name="owner" /></td>
+ <td>
+ <input type="submit" name="add" value="Add" />
+ </td>
+ </form>
+ </tr>
+ <?cs each:item = admin.components ?>
+ <tr>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>">
+ <td><input type="text" name="newname" value="<?cs
var:item.name ?>" /></td>
+ <td><input type="text" name="newowner" value="<?cs
var:item.owner ?>" /></td>
+ <td>
+ <input type="submit" name="update" value="Update"/>
+ <input type="submit" name="delete" value="Delete"/>
+ <input type="hidden" name="name" value="<?cs
var:item.name ?>" />
+ <input type="hidden" name="owner" value="<?cs
var:item.owner ?>" />
+ </td>
+ </form>
+ </tr>
+ <?cs /each ?>
+ </table>
+
+ <?cs elif admin.type == "versions" ?>
+
+ <h1>Versions</h1>
+ <table>
+ <tr>
+ <td>Name</td>
+ <td>Time</td>
+ </tr>
+ <tr>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>">
+ <td><input type="text" name="name" /></td>
+ <td><input type="text" name="time" /></td>
+ <td>
+ <input type="submit" name="add" value="Add" />
+ </td>
+ </form>
+ </tr>
+ <?cs each:item = admin.versions ?>
+ <tr>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>">
+ <td><input type="text" name="newname" value="<?cs
var:item.name ?>" /></td>
+ <td><input type="text" name="newtime" value="<?cs
var:item.time ?>" /></td>
+ <td>
+ <input type="submit" name="update" value="Update"/>
+ <input type="submit" name="delete" value="Delete"/>
+ <input type="hidden" name="name" value="<?cs
var:item.name ?>" />
+ </td>
+ </form>
+ </tr>
+ <?cs /each ?>
+ </table>
+
+ <?cs elif admin.type == "milestones" ?>
+
+ <h1>Milestones</h1>
+ <table>
+ <tr>
+ <td>Name</td>
+ <td>Time</td>
+ </tr>
+ <tr>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>">
+ <td><input type="text" name="name" /></td>
+ <td><input type="text" name="time" /></td>
+ <td>
+ <input type="submit" name="add" value="Add" />
+ </td>
+ </form>
+ </tr>
+ <?cs each:item = admin.milestones ?>
+ <tr>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>">
+ <td><input type="text" name="newname" value="<?cs
var:item.name ?>" /></td>
+ <td><input type="text" name="newtime" value="<?cs
var:item.time ?>" /></td>
+ <td>
+ <input type="submit" name="update" value="Update"/>
+ <input type="submit" name="delete" value="Delete"/>
+ <input type="hidden" name="name" value="<?cs
var:item.name ?>" />
+ </td>
+ </form>
+ </tr>
+ <?cs /each ?>
+ </table>
+
+ <?cs elif admin.type == "priorities" ?>
+
+ <h1>Priorities</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs var:admin.type
?>" method="get">
+ <?cs call:hdf_editlist(enums.priority) ?><br />
+ </form>
+
+ <?cs elif admin.type == "severities" ?>
+
+ <h1>Severities</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs var:admin.type
?>" method="get">
+ <?cs call:hdf_editlist(enums.severity) ?><br />
+ </form>
+
+ <?cs elif admin.type == "permissions" ?>
+
+ <h1>Permissions</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs var:admin.type
?>" method="get">
+ User: <br />
+ <input type="text" name="user" value="<?cs var:admin.user
?>" />
+ <input type="submit" name="view" value="View" />
+<?cs if admin.user ?>
+ <br />
+ <br />
+ <table>
+ <tr>
+ <td>
+ <!--?cs call:hdf_editlist(enums.severity) ?><br /-->
+ Available Permissions:<br />
+ <select size="15" multiple="true" name="permissions.avail">
+ <?cs each:item = admin.permissions.avail ?>
+ <option><?cs var:item.name ?></option>
+ <?cs /each ?>
+ </select>
+ <br />
+ <input type="submit" name="add" value="Add" />
+ </td><td>
+ Granted Permissions: <br />
+ <select size="15" multiple="true" name="permissions.grant">
+ <?cs each:item = admin.permissions.grant ?>
+ <option><?cs var:item.name ?></option>
+ <?cs /each ?>
+ </select>
+ <br />
+ <input type="submit" name="delete" value="Remove" />
+ </td>
+ </tr>
+ </table>
+ </form>
+
+<?cs /if ?>
+<?cs /if ?>
+
+ </div>
+</div>
+</div>
+<?cs include:"footer.cs"?>
+
+<!-- alternate UI (not functional)
+
+ <table>
+ <tr>
+ <td>
+ <h1>Components</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>" method="get">
+ <input type="hidden" name="type" value="<?cs
var:admin.type ?>" />
+ <?cs call:hdf_editlist(admin.components) ?><br />
+ </form>
+ </td><td>
+ <h1>Versions</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>" method="get">
+ <input type="hidden" name="type" value="<?cs
var:admin.type ?>" />
+ <?cs call:hdf_editlist(admin.versions) ?><br />
+ </form>
+ </td><td>
+ <h1>Milestones</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>" method="get">
+ <input type="hidden" name="type" value="<?cs
var:admin.type ?>" />
+ <?cs call:hdf_editlist(admin.milestones) ?><br />
+ </form>
+ </td>
+ </tr><tr>
+ <td>
+ <h1>Priorities</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>" method="get">
+ <input type="hidden" name="type" value="<?cs
var:admin.type ?>" />
+ <?cs call:hdf_editlist(enums.priority) ?><br />
+ </form>
+ </td><td>
+ <h1>Severities</h1>
+ <form action="<?cs var:cgi_location ?>/admin/<?cs
var:admin.type ?>" method="get">
+ <input type="hidden" name="type" value="<?cs
var:admin.type ?>" />
+ <?cs call:hdf_editlist(enums.severity) ?><br />
+ </form>
+ </td>
+ </tr>
+ </table>
+
+-->
Re: On-line admin [ In reply to ]
Laurie Harper wrote:
*snip*
>>> - you can't delete multiple items; it's a UI design limitation for
>>> most stuff, but for priorities, severities and permissions it's
>>> because I haven't figured out a way to access all the values of a
>>> multi-valued HTTP request parameter yet
>>>
>> This is true, currently only the first value for each key is kept
>> in the self.args dictionary. This is bad. It's probably best to convert
>> the code to use the fieldstorage directly.
>
>
> I had a look at the Request/CGIRequest classes and I don't think it'll
> be a problem to extend to support this; just adding a get_all() that
> returns a list for multi-valued parameters should do the trick.
>
self.args is now a true FieldStorage instance. so
self.args.getvalue('foo') will return a list for multi-valued
parameters.

This change touched a lot of files so perhaps I broke something...

/ Jonas
--
Jonas Borgström | Edgewall Software
jonas@edgewall.com | Professional GNU/Linux & Open Source Consulting.
| http://www.edgewall.com/
Re: On-line admin [ In reply to ]
Jonas Borgström wrote:
> self.args is now a true FieldStorage instance. so
> self.args.getvalue('foo') will return a list for multi-valued
> parameters.

That'll do the trick :)

> This change touched a lot of files

No kidding...

> so perhaps I broke something...

My admin module for one :) I'll have to fix that up, then I can
add multiple deletes.

Thanks,

L.
On-line admin [ In reply to ]
Laurie Harper,

I found your online admin patch in the mailing list archive and I'm
trying to get it working.

It appears that the web presentation of your patch doesn't handle the
whitespace correctly. I've manually fixed this (I think) but I appear
to be missing the file named /usr/share/trac/htdocs/css/admin.css.

Do you have an updated patch file that includes admin.css? Maybe you
even have one that was diffed against 0.7.1?

Thanks.

--
Tim Moloney
ManTech Real-time Systems Laboratory
2015 Cattlemen Road \ /
Sarasota, FL 34232 .________\(O)/________.
(941) 377-6775 x208 ' ' O(.)O ' '
Re: On-line admin [ In reply to ]
I didn't get as far as putting together a CSS for it; I posted an
initial version of the patch on a 'is this the right way to go' basis
but there didn't seem to be much interest so I never completed it.

L.

Tim Moloney wrote:
> Laurie Harper,
>
> I found your online admin patch in the mailing list archive and I'm
> trying to get it working.
>
> It appears that the web presentation of your patch doesn't handle the
> whitespace correctly. I've manually fixed this (I think) but I appear
> to be missing the file named /usr/share/trac/htdocs/css/admin.css.
>
> Do you have an updated patch file that includes admin.css? Maybe you
> even have one that was diffed against 0.7.1?
>
> Thanks.
>
Re: On-line admin [ In reply to ]
Laurie Harper wrote:

> I didn't get as far as putting together a CSS for it; I posted an
> initial version of the patch on a 'is this the right way to go' basis
> but there didn't seem to be much interest so I never completed it.

I'm willing to help with this, assuming that I have the necessary
skills.

Having said that, I'm short on time and I don't what to spend it
developing something that won't get used or will be replaced. First, I
would like to know what plans, if any, Edgewall has for web-based
administration. Is it a planned feature that they will implement? Will
they accept your implementation or did they have another design in mind?
I see in the roadmap that 0.9 is scheduled to have a "web configuration
module" but I'm not sure this is the same as the web-based
administration.

It's not that I'm not willing to help, I just want to use my time in the
best way possible.

--
Tim Moloney
ManTech Real-time Systems Laboratory
2015 Cattlemen Road \ /
Sarasota, FL 34232 .________\(O)/________.
(941) 377-6775 x208 ' ' O(.)O ' '
Re: On-line admin [ In reply to ]
Tim Moloney wrote:

> Laurie Harper wrote:
>
>> I didn't get as far as putting together a CSS for it; I posted an
>> initial version of the patch on a 'is this the right way to go' basis
>> but there didn't seem to be much interest so I never completed it.
>
>
> I'm willing to help with this, assuming that I have the necessary
> skills.
>
Great!

> Having said that, I'm short on time and I don't what to spend it
> developing something that won't get used or will be replaced. First, I
> would like to know what plans, if any, Edgewall has for web-based
> administration. Is it a planned feature that they will implement? Will
> they accept your implementation or did they have another design in mind?
> I see in the roadmap that 0.9 is scheduled to have a "web configuration
> module" but I'm not sure this is the same as the web-based
> administration.
>
> It's not that I'm not willing to help, I just want to use my time in the
> best way possible.
>
Yes, we plan to include some type of web-based administration before
Trac 1.0. We haven't started working on it ourself so any contribution
will of course be appreciated.

The best approach is probably to begin with a simple
"prototype" that only support a small subset of the planned
functionality. That way we can get it into trunk as fast as
possible and then gradually add more and more functionality.

This module will probably duplicate most of the functionality from
the trac-admin program so refactoring common code into a shared
configuration api is probably a good idea as well.

If you or anybody else decide to give this a try dropping by
#trac on freenode is probably a good idea.

Thanks,
Jonas
--
Jonas Borgstr?m | Edgewall Software
jonas@edgewall.com | Professional GNU/Linux & Open Source Consulting.
| http://www.edgewall.com/
Re: On-line admin [ In reply to ]
This is an area of interest for me as well, as we have several new projects starting up each month, and we would like to have a separate trac instance for each one. Bringing admin functionality into the web interface would make it much more likely that features like versions, milestones and components are used across the board.

I could also provide some help with this. Tim/Laurie - have you made a decission to do anything on this? Have you produced any architecture or user interface design documentation? (unfortunately I can't find Laurie's original mail on the archives).

In the meantime, is there a way to 'officially' submit the patch I posted on the mailing list last week for re-syncing subversion repos with the trac DB using trac-admin. I've now noticed there appears to be an assigned ticket #347 on this subject, but no fix yet.

Ian.

-----Original Message-----
From: trac-bounces@lists.edgewall.com on behalf of Jonas Borgström
Sent: Fri 6/18/2004 16:34
To: moloney@mrsl.com
Cc: trac@lists.edgewall.com
Subject: Re: [Trac] Re: On-line admin



Tim Moloney wrote:

> Laurie Harper wrote:
>
>> I didn't get as far as putting together a CSS for it; I posted an
>> initial version of the patch on a 'is this the right way to go' basis
>> but there didn't seem to be much interest so I never completed it.
>
>
> I'm willing to help with this, assuming that I have the necessary
> skills.
>
Great!

> Having said that, I'm short on time and I don't what to spend it
> developing something that won't get used or will be replaced. First, I
> would like to know what plans, if any, Edgewall has for web-based
> administration. Is it a planned feature that they will implement? Will
> they accept your implementation or did they have another design in mind?
> I see in the roadmap that 0.9 is scheduled to have a "web configuration
> module" but I'm not sure this is the same as the web-based
> administration.
>
> It's not that I'm not willing to help, I just want to use my time in the
> best way possible.
>
Yes, we plan to include some type of web-based administration before
Trac 1.0. We haven't started working on it ourself so any contribution
will of course be appreciated.

The best approach is probably to begin with a simple
"prototype" that only support a small subset of the planned
functionality. That way we can get it into trunk as fast as
possible and then gradually add more and more functionality.

This module will probably duplicate most of the functionality from
the trac-admin program so refactoring common code into a shared
configuration api is probably a good idea as well.

If you or anybody else decide to give this a try dropping by
#trac on freenode is probably a good idea.

Thanks,
Jonas
--
Jonas Borgström | Edgewall Software
jonas@edgewall.com | Professional GNU/Linux & Open Source Consulting.
| http://www.edgewall.com/

_______________________________________________
Trac mailing list
Trac@lists.edgewall.com
http://lists.edgewall.com/mailman/listinfo/trac


-------------- next part --------------
z'µìmjÛZržžÜ²Ç+¹¶ÞtÖ¦zz-jö¢•¦åy<©yªi–'¶*'þk-çÒ‹7¼ïŸÊ׬ ëž‹Z½¨¥i¹^R¹j·!Š÷¿¶¶œý«miÈfz{lÿm4ãN¶ß÷¼ßÇ8wßÚ¶Öœ†g§µ¸§From Daragh@UChicago.edu Wed Jun 23 10:58:44 2004
From: Daragh@UChicago.edu (Daragh Fitzpatrick)
Date: Thu Jul 22 22:37:00 2004
Subject: [Trac] Ticket enhancements
Message-ID: <200406231558.i5NFwim7010963@relay00.uchicago.edu>


Hi,

are there any currently any plans to enhance the ticketing
subsystem?

Specifically, we are moving from a defect tracking system that
splits out what is currently 'description' in Trac into description, steps
to reproduce, workaround and resolution. We really like this format, and
would like to see it in Trac.

In addition, because we will be making the new ticket fucntion to
the entire user-base, we are considering changing the initial assignee field
to being a drop-down of Trac users, although personally I like it to be open
and defaulted - we are also considering hiding it altogether.

Another enhancement would be whatever is needed to make this a
generic full change management tool, so that it could handle config change
requests, deployment requests, etc. as well as defects/enhancements. This
may require the addition of a field or two, and it would be nice to have
some sort of strong (automated) link with SVN, so that there is impact-type
information w.r.t. what files were changed to resolve what ticket.

I know this is a lot, so I was initially was just wondering what the
plans for tickets are, and if we wanted to enhance it ourselves, would be it
in line with the product's direction? (I.e., rolled back into the product)

Thanks!

Cheers,

:D

--------------------------------------------------------------------
Daragh Fitzpatrick Daragh@UChicago.edu (773) 702-8976

Solutions Architect NSIT Administrative Systems
Renewal Projects and Architecture University of Chicago
--------------------------------------------------------------------
Re: On-line admin [ In reply to ]
Ian Leader wrote:

> I could also provide some help with this. Tim/Laurie - have you made a
> decission to do anything on this?

I have started working on pulling out the admin functions into a
separate file for modularity. Conceptually, this isn't hard but
it's taking me a little bit of time. I'm relatively new to Python
so I still run into issues that I need to learn about.

> Have you produced any architecture or user interface design documentation?

Since I'm new to Python and don't know the best way to use its
capabilities, I'm doing a "bottom-up approach", which means that
I'm implementing it, figuring out what works along the way, then
I'll document it.

My plan is to create a base class for admin commands, then create
child classes for the command types (version, milestone, priority,
etc.). So far, I've implemented the base class, the enum class
(priority and severity), and the timestamp class (milestone and
version). Things appear to be going well but I want to finish
converting the other command types before I can confidently say
that my design works.

Once I'm done (early next week?) I'll post my changes. If the
code isn't too horrible, maybe it'll be accepted.

> (unfortunately I can't find Laurie's original mail on the archives).

http://lists.edgewall.com/archive/trac/msg00171.html
http://lists.edgewall.com/archive/trac/msg00410.html

> In the meantime, is there a way to 'officially' submit the patch I posted on
> the mailing list last week for re-syncing subversion repos with the
trac DB
> using trac-admin. I've now noticed there appears to be an assigned ticket
> #347 on this subject, but no fix yet.

--
Tim Moloney
ManTech Real-time Systems Laboratory
2015 Cattlemen Road \ /
Sarasota, FL 34232 .________\(O)/________.
(941) 377-6775 x208 ' ' O(.)O ' '
Re: On-line admin [ In reply to ]
Tim Moloney wrote:

> Ian Leader wrote:
>
>> I could also provide some help with this. Tim/Laurie - have you made a
>
> > decission to do anything on this?
>
> I have started working on pulling out the admin functions into a
> separate file for modularity. Conceptually, this isn't hard but
> it's taking me a little bit of time. I'm relatively new to Python
> so I still run into issues that I need to learn about.
>
>> Have you produced any architecture or user interface design
>> documentation?
>
>
> Since I'm new to Python and don't know the best way to use its
> capabilities, I'm doing a "bottom-up approach", which means that
> I'm implementing it, figuring out what works along the way, then
> I'll document it.
>
I think another approach might make it easier to integrate the whole
thing into trunk as fast as possible.

1. Create a simple first version of the configuration module
by copying (duplicate) relevant parts from trac-admin.py. The
resulting patch will not touch much of the existing code and can
thereby easily be integrated into trunk.

2. Incrementally add more and more features and refactor out common
code into a new admin-api. Other people can easily help you with
this part.

How does that sound to you?

/ Jonas
--
Jonas Borgstr?m | Edgewall Software
jonas@edgewall.com | Professional GNU/Linux & Open Source Consulting.
| http://www.edgewall.com/
Re: On-line admin [ In reply to ]
Tim Moloney wrote:

> I have started working on pulling out the admin functions into a
> separate file for modularity. Conceptually, this isn't hard but
> it's taking me a little bit of time. I'm relatively new to Python
> so I still run into issues that I need to learn about.
>
> [...]
>
> Once I'm done (early next week?) I'll post my changes. If the
> code isn't too horrible, maybe it'll be accepted.

I have split the trac-admin functionality into trac-admin and
admin_cmd.py. The idea is that trac-admin would only contain
the text user interface and all of the actual administration is
in admin_cmd.py. Then admin_cmd.py could be used by other user
interfaces (web, gui, etc.).

Although this is mostly true, I have not yet moved the 'initenv'
and 'update' commands (and their support functions) to admin_cmd.py.
Regardless, I think that enough is there for people to see if
this is a good implementation (or if I should stop programming
in Python altogether).

--
Tim Moloney
ManTech Real-time Systems Laboratory
2015 Cattlemen Road \ /
Sarasota, FL 34232 .________\(O)/________.
(941) 377-6775 x208 ' ' O(.)O ' '
-------------- next part --------------
#!/usr/bin/python


import os
import sys
import time

from trac import util
import trac.siteconfig
import trac.Environment

#
# Administration command base class
#

class admin_cmd:

def __init__(self, env_dir, type):
self.__env = None
self.env_dir = env_dir
self.type = type

def db_open(self):
try:
if not self.__env:
self.__env = trac.Environment.Environment(self.env_dir)
return self.__env.get_db_cnx()
except Exception, e:
print 'Failed to open environment.', e
sys.exit(1)

def db_execsql(self, sql, cursor=None):
data = []
if not cursor:
cnx=self.db_open()
cursor = cnx.cursor()
else:
cnx = None
cursor.execute(sql)
while 1:
row = cursor.fetchone()
if row == None:
break
data.append(row)
if cnx:
cnx.commit()
return data


#
# Initenv class
#

class initenv(admin_cmd):

def __init__(self, env_dir, type):
trac.admin_cmd.admin_cmd.__init__(self, env_dir, type)

def env_check(self):
try:
self.__db = trac.Environment.Environment(self.envname)
except:
return 0
return 1

def run(self):
if self.env_check():
print "Initdb for '%s' failed." % self.envname
print 'Does a environment already exist?'
return


#
# Wiki class
#

class wiki(admin_cmd):

def __init__(self, env_dir, type):
trac.admin_cmd.admin_cmd.__init__(self, env_dir, type)

def list(self):
data = self.db_execsql("SELECT name,max(version),time"
" FROM wiki GROUP BY name ORDER BY name")
ldata = [(d[0], d[1], time.ctime(d[2])) for d in data]
return ldata

def import_cmd(self, filename, title, cursor=None):
if not os.path.isfile(filename):
raise Exception('%s is not a file' % filename)
f = open(filename, 'r')
data = util.to_utf8(f.read())

# Make sure we don't insert the exact same page twice
old = self.db_execsql("SELECT text FROM wiki WHERE name='%s'"
" ORDER BY version DESC LIMIT 1" % title, cursor)
if old and data == old[0][0]:
raise Exception('%s already up to date.' % title)

data = data.replace("'", "''") # Escape ' for safe SQL
f.close()

sql = ("INSERT INTO wiki('version','name','time','author','ipnr','text') "
" SELECT 1+ifnull(max(version),0),'%(title)s','%(time)s','%(author)s',"
" '%(ipnr)s','%(text)s' FROM wiki WHERE name='%(title)s'"
% {'title':title,
'time':int(time.time()),
'author':'trac',
'ipnr':'127.0.0.1',
'locked':'0',
'text':data})
self.db_execsql(sql, cursor)

def export_cmd(self, page, filename=''):
data=self.db_execsql("SELECT text FROM wiki WHERE name='%s'"
" ORDER BY version DESC LIMIT 1" % page)
text = data[0][0]
if not filename:
return text
else:
if os.path.isfile(filename):
raise Exception("File '%s' exists" % filename)
f = open(filename, 'w')
f.write(text)
f.close()

def dump_cmd(self, dir):
data = self.db_execsql('SELECT DISTINCT name FROM wiki')
pages = [r[0] for r in data]
for p in pages:
dst = os.path.join(dir, p)
#print " %s => %s" % (p, dst)
self.export_cmd(p, dst)

def load_cmd(self, dir, cursor=None, ignore=[]):
for page in os.listdir(dir):
if page in ignore:
continue
filename = os.path.join(dir, page)
if os.path.isfile(filename):
#print " %s => %s" % (filename, page)
self.import_cmd(filename, page, cursor)


#
# Permission class
#

class permission(admin_cmd):

def __init__(self, env_dir, type):
trac.admin_cmd.admin_cmd.__init__(self, env_dir, type)

def list(self):
data = self.db_execsql('SELECT username, action FROM permission')
return data

def add(self, user, action):
self.db_execsql("INSERT INTO permission VALUES('%s', '%s')"
% (user, action.upper()))

def remove(self, user, action):
self.db_execsql("DELETE FROM permission WHERE username='%s'"
" AND action='%s'" % (user, action.upper()))


#
# Component class
#

class component(admin_cmd):

def __init__(self, env_dir, type):
trac.admin_cmd.admin_cmd.__init__(self, env_dir, type)

def list(self):
data = self.db_execsql("SELECT name, owner FROM component")
return data

def add(self, name, owner):
data = self.db_execsql("INSERT INTO component VALUES('%s', '%s')"
% (name, owner))

def rename(self, name, newname):
cnx = self.db_open()
cursor = cnx.cursor ()
self.db_execsql("UPDATE component SET name='%s' WHERE name='%s'"
% (newname,name), cursor)
self.db_execsql("UPDATE ticket SET component='%s' WHERE component='%s'"
% (newname,name), cursor)
cnx.commit()

def remove(self, name):
data = self.db_execsql("DELETE FROM component WHERE name='%s'"
% (name))

def set_owner(self, name, owner):
data = self.db_execsql("UPDATE component SET owner='%s'"
" WHERE name='%s'" % (owner,name))


#
# Enum class (priority and severity)
#

class enum(admin_cmd):

def __init__(self, env_dir, type):
trac.admin_cmd.admin_cmd.__init__(self, env_dir, type)

def list(self):
data = self.db_execsql("SELECT name FROM enum WHERE type='%s'"
% self.type)
return data

def add(self, name):
sql = ("INSERT INTO enum('value','type','name') "
" SELECT 1+ifnull(max(value),0),'%(type)s','%(name)s'"
" FROM enum WHERE type='%(type)s'"
% {'type':self.type, 'name':name})
data = self.db_execsql(sql)

def change(self, name, newname):
d = {'name':name, 'newname':newname, 'type':self.type}
data = self.db_execsql("SELECT name FROM enum"
" WHERE type='%(type)s'"
" AND name='%(name)s'" % d)
if not data:
raise Exception, "No such value '%s'" % name
data = self.db_execsql("UPDATE enum SET name='%(newname)s'"
" WHERE type='%(type)s'"
" AND name='%(name)s'" % d)

def remove(self, name):
data = self.db_execsql("SELECT name FROM enum"
" WHERE type='%s'"
" AND name='%s'" % (self.type, name))
if not data:
raise Exception, "No such value '%s'" % name
data = self.db_execsql("DELETE FROM enum WHERE type='%s' AND name='%s'"
% (self.type, name))


#
# Timestamp class (milestone and version)
#

class timestamp(admin_cmd):

def __init__(self, env_dir, type):
trac.admin_cmd.admin_cmd.__init__(self, env_dir, type)

def list(self):
data = self.db_execsql("SELECT name,time FROM %s ORDER BY time,name"
% self.type)
data = map(lambda x: (x[0], x[1] and time.strftime('%c', time.localtime(x[1]))), data)
return data

def add(self, name):
sql = ("INSERT INTO %(type)s('name') VALUES('%(name)s')"
% {'type':self.type, 'name':name})
data = self.db_execsql(sql)

def time(self, name, t):
seconds = None
t = t.strip()
if t == 'now':
seconds = int(time.time())
else:
for format in [.'%x %X', '%x, %X', '%X %x', '%X, %x', '%x', '%c',
'%b %d, %Y']:
try:
pt = time.strptime(t, format)
print pt
seconds = str(time.mktime(pt))
except ValueError:
continue
break
if seconds == None:
try:
seconds = int(t)
except ValueError:
pass
if seconds != None:
data = self.db_execsql("UPDATE %s SET time='%s' WHERE name='%s'"
% (self.type, seconds, name))
else:
print >> sys.stderr, 'Unknown time format'

def rename(self, name, newname):
d = {'name':name, 'newname':newname, 'type':self.type}
data = self.db_execsql("SELECT name FROM %(type)s"
" WHERE name='%(name)s'" % d)
if not data:
raise Exception, "No such %s '%s'" % (self.type, name)
data = self.db_execsql("UPDATE %(type)s SET name='%(newname)s'"
" WHERE name='%(name)s'" % d)

def remove(self, name):
d = {'name':name, 'type':self.type}
data = self.db_execsql("SELECT name FROM %(type)s"
" WHERE name='%(name)s'" % d)
if not data:
raise Exception, "No such %s '%s'" % (self.type, name)
data = self.db_execsql("DELETE FROM %(type)s"
" WHERE name='%(name)s'" % d)
-------------- next part --------------
#!/usr/bin/python

# -*- coding: iso8859-1 -*-
__author__ = 'Daniel Lundin <daniel@edgewall.com>, Jonas Borgström <jonas@edgewall.com>'
__copyright__ = 'Copyright (c) 2004 Edgewall Software'
__license__ = """
Copyright (C) 2003, 2004 Edgewall Software
Copyright (C) 2003, 2004 Jonas Borgström <jonas@edgewall.com>
Copyright (C) 2003, 2004 Daniel Lundin <daniel@edgewall.com>

Trac is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.

Trac is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA."""

import os
import os.path
import sys
import time
import cmd
import shlex
import sqlite
import StringIO

from trac import util
import trac.siteconfig
import trac.Environment

import trac.admin_cmd


def my_sum(list):
"""Python2.1 doesn't have sum()"""
tot = 0
for item in list:
tot += item
return tot


class TracAdmin(cmd.Cmd):
intro = ''
license = trac.__license_long__
credits = trac.__credits__
doc_header = 'Trac Admin Console %(ver)s\n' \
'Available Commands:\n' \
% {'ver':trac.__version__ }
ruler = ''
prompt = "Trac> "
__env = None

def __init__(self,envdir=None):
cmd.Cmd.__init__(self)
self.interactive = 0
if envdir:
self.env_set(envdir)

def docmd(self, cmd='help'):
self.onecmd(cmd)

def run(self):
self.interactive = 1
print 'Welcome to trac-admin %(ver)s\n' \
'Interactive Trac adminstration console.\n' \
'%(copy)s\n\n' \
"Type: '?' or 'help' for help on commands.\n" % \
{'ver':trac.__version__,'copy':__copyright__}
while 1:
try:
self.cmdloop()
break
except KeyboardInterrupt:
print "\n** Interrupt. Use 'quit' to exit **"

##
## Environment methods
##

def env_set(self, envname):
self.envname = envname
self.prompt = "Trac [%s]> " % self.envname

def env_check(self):
try:
self.__db = trac.Environment.Environment(self.envname)
except:
return 0
return 1

def env_create(self):
try:
self.__env = trac.Environment.Environment (self.envname, create=1)
return self.__env
except Exception, e:
print 'Failed to create environment.', e
sys.exit(1)

def db_open(self):
try:
if not self.__env:
self.__env = trac.Environment.Environment (self.envname)
return self.__env.get_db_cnx()
except Exception, e:
print 'Failed to open environment.', e
sys.exit(1)

def db_execsql (self, sql, cursor=None):
data = []
if not cursor:
cnx=self.db_open()
cursor = cnx.cursor()
else:
cnx = None
cursor.execute(sql)
while 1:
row = cursor.fetchone()
if row == None:
break
data.append(row)
if cnx:
cnx.commit()
return data

##
## Utility methods
##

def arg_tokenize (self, argstr):
if hasattr(shlex, 'split'):
toks = shlex.split(argstr)
else:
def my_strip(s, c):
"""string::strip in python2.1 doesn't support arguments"""
i = j = 0
for i in range(len(s)):
if not s[i] in c:
break
for j in range(len(s), 0, -1):
if not s[j-1] in c:
break
return s[i:j]

lexer = shlex.shlex(StringIO.StringIO(argstr))
lexer.wordchars = lexer.wordchars + ".,_/"
toks = []
while 1:
token = my_strip(lexer.get_token(), '"\'')
if not token:
break
toks.append(token)
return toks or ['']

def word_complete (self, text, words):
return [a for a in words if a.startswith (text)]

def print_listing(self, headers, data, sep=' ',decor=1):
ldata = data
if decor:
ldata.insert (0, headers)
print
colw=[]
ncols = len(ldata[0]) # assumes all rows are of equal length
for cnum in xrange(0, ncols):
mw = 0
for cell in [str(d[cnum]) or '' for d in ldata]:
if len(cell) > mw:
mw = len(cell)
colw.append(mw)
for rnum in xrange(0, len(ldata)):
for cnum in xrange(0, ncols):
if decor and rnum == 0:
sp = ('%%%ds' % len(sep)) % ' ' # No separator in header
else:
sp = sep
if cnum+1 == ncols: sp = '' # No separator after last column
print ("%%-%ds%s" % (colw[cnum], sp)) % (ldata[rnum][cnum] or ''),
print
if rnum == 0 and decor:
print ''.join(['-' for x in xrange(0,(1+len(sep))*cnum+my_sum(colw))])
print

def print_doc(self,doc,decor=0):
if not doc: return
self.print_listing (['Command','Description'], doc, ' --', decor)

##
## Available Commands
##


#
# About
#
_help_about = [('about', 'Shows information about trac-admin')]

def do_about(self, line):
print
print 'Trac Admin Console %s' % trac.__version__
print '================================================================='
print self.license
print self.credits


#
# Help
#
_help_help = [ ('help', 'Show documentation') ]

def do_help(self, line=None):
arg = self.arg_tokenize(line)
if arg[0]:
try:
doc = getattr(self, "_help_" + arg[0])
self.print_doc (doc)
except AttributeError:
print "No documentation found for '%s'" % arg[0]
else:
docs = (self._help_about + self._help_help +
self._help_initenv + self._help_upgrade +
self._help_wiki +
self._help_permission + self._help_component +
self._help_priority + self._help_severity +
self._help_version + self._help_milestone)
print 'trac-admin - The Trac Administration Console %s' \
% trac.__version__
if not self.interactive:
print
print "Usage: trac-admin <dbfile> [command [subcommand]" \
"[option ...]]\n"
print "Invoking trac-admin without command starts " \
"interactive mode."
self.print_doc(docs)
print self.credits


#
# Initdb
#
_help_initenv = [ ('initenv', 'Create and initializes a new environment') ]

def do_initdb(self, line):
self.do_initenv(line)

def do_initenv(self, line):
if self.env_check():
print "Initdb for '%s' failed.\nDoes aa environment already exist?" % self.envname
return
try:
print 'Creating a new Trac environment at %s' % self.envname
print
print 'Trac will first ask a few questions about your environment '
print 'in order to initalize and prepare the project database.'
print
print " Please enter the name of your project."
print " This name will be used in page titles and descriptions."
print
dp = 'My Project'
project_name = raw_input('Project Name [%s]> ' % dp) or dp

from svn import util, repos, core
core.apr_initialize()
pool = core.svn_pool_create(None)
print
print ' Please specify the absolute path to the project Subversion repository.'
print ' Repository must be local, and trac-admin requires read+write'
print ' permission to initialize the Trac database.'
print
while 1:
try:
drp = '/var/svn/test'
prompt = 'Path to repository [%s]> ' % drp
repository_dir = raw_input(prompt) or drp

# Remove any trailing slash or else subversion might abort
if not os.path.split(repository_dir)[1]:
repository_dir = os.path.split(repository_dir)[0]

rep = repos.svn_repos_open(repository_dir, pool)
fs_ptr = repos.svn_repos_fs(rep)
break
except KeyboardInterrupt:
raise KeyboardInterrupt
except Exception, e:
print repository_dir, 'Repository access error: %s' % e

print
print ' Please enter location of Trac page templates.'
print ' Default is the location of the site-wide templates' \
'installed with Trac.'

print
dt = trac.siteconfig.__default_templates_dir__
prompt = 'Templates directory [%s]> ' % dt
while 1:
templates_dir = raw_input(prompt) or dt
if os.access(os.path.join(templates_dir, 'browser.cs'), os.F_OK) and \
os.access(os.path.join(templates_dir, 'ticket.cs'), os.F_OK):
break
print templates_dir, 'doesn\'t look like a Trac templates directory'

except KeyboardInterrupt:
print '\n* initdb aborted'
return
try:
print 'Creating and Initializing Project'
self.env_create()
cnx = self.__env.get_db_cnx()
print ' Inserting default data'
self.__env.insert_default_data()

print ' Configuring Project'
print ' trac.repository_dir'
self.__env.set_config('trac', 'repository_dir', repository_dir)
print ' trac.templates_dir'
self.__env.set_config('trac', 'templates_dir', templates_dir)
print ' project.name'
self.__env.set_config('project', 'name', project_name)
self.__env.save_config()
# Add a few default wiki pages
print ' Installing wiki pages'
cursor = cnx.cursor()
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
cmd.load_cmd(trac.siteconfig.__default_wiki_dir__, cursor)

from trac import sync
print ' Indexing repository'
sync.sync(cnx, rep, fs_ptr, pool)
print 'Done.'
except Exception, e:
print 'Failed to initialize database.', e
sys.exit(2)


print "---------------------------------------------------------------------"
print
print 'Project database for \'%s\' created.' % project_name
print
print ' Customize settings for your project using the command:'
print
print ' trac-admin %s' % self.envname
print
print ' Don\'t forget, you also need to copy (or symlink) "trac/cgi-bin/trac.cgi"'
print ' to you web server\'s /cgi-bin/ directory, and then configure the server.'
print
print ' If you\'re using Apache, this config example snippet might be helpful:'
print
print ' Alias /trac "/wherever/you/installed/trac/htdocs/"'
print ' <Location "/cgi-bin/trac.cgi">'
print ' SetEnv TRAC_ENV "%s"' % self.envname
print ' </Location>'
print
print ' # You need something like this to authenticate users'
print ' <Location "/cgi-bin/trac.cgi/login">'
print ' AuthType Basic'
print ' AuthName "%s"' % project_name
print ' AuthUserFile /somewhere/trac.htpasswd'
print ' Require valid-user'
print ' </Location>'
print

print ' The latest documentation can also always be found on the project website:'
print ' http://projects.edgewall.com/trac/'
print
print 'Congratulations!'
print


#
# Upgrade
#
_help_upgrade = [ ('upgrade', 'Upgrade database to current version.') ]

def do_upgrade(self, line):
arg = self.arg_tokenize(line)
do_backup = 1
if arg[0] in ['-b', '--no-backup']:
do_backup = 0
db = self.db_open()
try:
curr = self.__env.get_version()
latest = trac.db_default.db_version
if curr < latest:
print "Upgrade: Upgrading %s to db version %i" \
% (self.envname, latest)
if do_backup:
print "Upgrade: Backup of old database saved in " \
"%s/db/trac.db.%i.bak" % (self.envname, curr)
else:
print "Upgrade: Backup disabled. Non-existant warranty voided."
self.__env.upgrade(do_backup)
else:
print "Upgrade: Database is up to date, no upgrade necessary."
except Exception, e:
print "Upgrade failed: ", e


#
# Wiki
#
_help_wiki = [ ('wiki list', 'List wiki pages'),
('wiki export <page> [file]',
'Export wiki page to file or stdout'),
('wiki import <page> [file]',
'Import wiki page from file or stdin'),
('wiki dump <directory>',
'Export all wiki pages to files named by title'),
('wiki load <directory>',
'Import all wiki pages from directory') ]

def get_wiki_list (self):
data = self.db_execsql('SELECT DISTINCT name FROM wiki')
return [r[0] for r in data]

def get_dir_list (self, pathstr,justdirs=0):
dname = os.path.dirname(pathstr)
d = os.path.join(os.getcwd(), dname)
dlist = os.listdir(d)
if justdirs:
result = []
for entry in dlist:
try:
if os.path.isdir(entry):
result.append(entry)
except:
pass
else:
result = dlist
return result

def complete_wiki(self, text, line, begidx, endidx):
argv = self.arg_tokenize(line)
argc = len(argv)
if line[-1] == ' ': # Space starts new argument
argc += 1
if argc==2:
comp = ['list','import','export','dump','load']
else:
if argv[1] in ['dump','load']:
comp = self.get_dir_list(argv[-1], 1)
elif argv[1] in ['export', 'import']:
if argc == 3:
comp = self.get_wiki_list()
elif argc == 4:
comp = self.get_dir_list(argv[-1])
return self.word_complete(text, comp)

def do_wiki(self, line):
arg = self.arg_tokenize(line)
try:
if arg[0] == 'list':
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
data = cmd.list()
self.print_listing(['Title', 'Edits', 'Modified'], data)
elif arg[0] == 'import' and len(arg) == 3:
title = arg[1]
file = arg[2]
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
data = cmd.import_cmd(file, title)
elif arg[0] == 'export' and len(arg) in [2,3]:
page = arg[1]
file = (len(arg) == 3 and arg[2]) or None
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
data = cmd.export_cmd(page, file)
elif arg[0] == 'dump' and len(arg) in [1,2]:
dir = (len(arg) == 2 and arg[1]) or ''
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
data = cmd.dump_cmd(dir)
elif arg[0] == 'load' and len(arg) in [1,2]:
dir = (len(arg) == 2 and arg[1]) or ''
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
data = cmd.load_cmd(dir)
elif arg[0] == 'upgrade' and len(arg) == 1:
cmd = trac.admin_cmd.wiki(self.envname, 'wiki')
data = cmd.load_cmd(trac.siteconfig.__default_wiki_dir__,
ignore=['WikiStart'])
else:
self.do_help('wiki')
except Exception, e:
print 'Wiki %s failed:' % arg[0], e


#
# Permission
#
_help_permission = [. ('permission list', 'List permission rules'),
('permission add <user> <action>',
'Add a new permission rule'),
('permission remove <user> <action>',
'Remove permission rule') ]

def do_permission(self, line):
arg = self.arg_tokenize(line)
try:
if arg[0] == 'list':
cmd = trac.admin_cmd.permission(self.envname, 'permission')
data = cmd.list()
self.print_listing(['User', 'Action'], data)
print
print 'Available actions:'
print ' LOG_VIEW, FILE_VIEW, CHANGESET_VIEW, BROWSER_VIEW, '
print ' TICKET_VIEW, TICKET_CREATE, TICKET_MODIFY, TICKET_ADMIN, '
print ' REPORT_VIEW, REPORT_CREATE, REPORT_MODIFY, REPORT_DELETE, REPORT_ADMIN, '
print ' WIKI_VIEW, WIKI_CREATE, WIKI_MODIFY, WIKI_DELETE, WIKI_ADMIN, '
print ' TIMELINE_VIEW and SEARCH_VIEW.'
print ' CONFIG_VIEW, TRAC_ADMIN.'
print
elif arg[0] == 'add' and len(arg) == 3:
user = arg[1]
action = arg[2]
cmd = trac.admin_cmd.permission(self.envname, 'permission')
data = cmd.add(user, action)
elif arg[0] == 'remove' and len(arg) == 3:
user = arg[1]
action = arg[2]
cmd = trac.admin_cmd.permission(self.envname, 'permission')
data = cmd.remove(user, action)
else:
self.do_help('permission')
except Exception, e:
print 'Permission %s failed:' % arg[0], e


#
# Component
#
_help_component = [. ('component list', 'Show available components'),
('component add <name> <owner>',
'Add a new component'),
('component rename <name> <newname>',
'Rename a component'),
('component remove <name>',
'Remove/uninstall component'),
('component chown <name> <owner>',
'Change component ownership') ]

def get_component_list (self):
data = self.db_execsql ("SELECT name FROM component")
return [r[0] for r in data]

def get_user_list (self):
data = self.db_execsql ("SELECT DISTINCT username FROM permission")
return [r[0] for r in data]

def complete_component (self, text, line, begidx, endidx):
if begidx in [16,17]:
comp = self.get_component_list()
elif begidx > 15 and line.startswith('component chown '):
comp = self.get_user_list()
else:
comp = ['list','add','rename','remove','chown']
return self.word_complete(text, comp)

def do_component(self, line):
arg = self.arg_tokenize(line)
try:
if arg[0] == 'list':
cmd = trac.admin_cmd.component(self.envname, 'component')
data = cmd.list()
self.print_listing(['Name', 'Owner'], data)
elif arg[0] == 'add' and len(arg) == 3:
name = arg[1]
owner = arg[2]
cmd = trac.admin_cmd.component(self.envname, 'component')
data = cmd.add(name, owner)
elif arg[0] == 'rename' and len(arg) == 3:
name = arg[1]
newname = arg[2]
cmd = trac.admin_cmd.component(self.envname, 'component')
data = cmd.rename(name, newname)
elif arg[0] == 'remove' and len(arg) == 2:
name = arg[1]
cmd = trac.admin_cmd.component(self.envname, 'component')
data = cmd.remove(name)
elif arg[0] == 'chown' and len(arg) == 3:
name = arg[1]
owner = arg[2]
cmd = trac.admin_cmd.component(self.envname, 'component')
data = cmd.set_owner(name, owner)
else:
self.do_help('component')
except Exception, e:
print 'Component %s failed:' % arg[0], e


#
# Priority
#
_help_priority = [. ('priority list', 'Show possible ticket priorities'),
('priority add <value>', 'Add a priority value option'),
('priority change <value> <newvalue>',
'Change a priority value'),
('priority remove <value>', 'Remove priority value') ]

def get_enum_list (self, type):
data = self.db_execsql("SELECT name FROM enum WHERE type='%s'" % type)
return [r[0] for r in data]

def complete_priority(self, text, line, begidx, endidx):
if begidx == 16:
comp = self.get_enum_list('priority')
elif begidx < 15:
comp = ['list','add','change','remove']
return self.word_complete(text, comp)

def do_priority(self, line):
self._do_enum('priority', line)

#
# Severity
#
_help_severity = [. ('severity list', 'Show possible ticket priorities'),
('severity add <value>', 'Add a severity value option'),
('severity change <value> <newvalue>',
'Change a severity value'),
('severity remove <value>', 'Remove severity value') ]

def complete_severity(self, text, line, begidx, endidx):
if begidx == 16:
comp = self.get_enum_list('severity')
elif begidx < 15:
comp = ['list','add','change','remove']
return self.word_complete(text, comp)

def do_severity(self, line):
self._do_enum('severity', line)


#
# Priority and Severity share the same datastructure and methods:
#
def _do_enum(self, type, line):
arg = self.arg_tokenize(line)
try:
if arg[0] == 'list':
cmd = trac.admin_cmd.enum(self.envname, type)
data = cmd.list()
self.print_listing(['Possible Values'], data)
elif arg[0] == 'add' and len(arg)==2:
name = arg[1]
cmd = trac.admin_cmd.enum(self.envname, type)
data = cmd.add(name)
elif arg[0] == 'change' and len(arg)==3:
name = arg[1]
newname = arg[2]
cmd = trac.admin_cmd.enum(self.envname, type)
data = cmd.change(name, newname)
elif arg[0] == 'remove' and len(arg)==2:
name = arg[1]
cmd = trac.admin_cmd.enum(self.envname, type)
data = cmd.remove(name)
else:
self.do_help (type)
except Exception, e:
print 'Priority %s failed:' % arg[0], e


#
# Milestone
#
_help_milestone = [ ('milestone list', 'Show milestones'),
('milestone add <name> [time]', 'Add milestone'),
('milestone rename <name> <newname>',
'Rename milestone'),
('milestone time <name> <time>',
'Set milestone date (Format: "Jun 3, 2003")'),
('milestone remove <name>', 'Remove milestone') ]

def get_milestone_list(self):
cmd = trac.admin_cmd.timestamp(self.envname, 'milestone')
data = cmd.list()
return [r[0] for r in data]

def complete_milestone(self, text, line, begidx, endidx):
if begidx in [15,17]:
comp = self.get_milestone_list()
elif begidx < 15:
comp = ['list','add','rename','time','remove']
return self.word_complete(text, comp)

def do_milestone(self, line):
self._do_timestamp(line, 'milestone')


#
# Version
#
_help_version = [ ('version list', 'Show versions'),
('version add <name> [time]', 'Add version'),
('version rename <name> <newname>',
'Rename version'),
('version time <name> <time>',
'Set version date (Format: "Jun 3, 2003")'),
('version remove <name>', 'Remove version') ]

def get_version_list(self):
data = self.db_execsql("SELECT name FROM version", 'version')
return self.get__list()

def complete_version(self, text, line, begidx, endidx):
if begidx in [15,17]:
comp = self.get_version_list()
elif begidx < 15:
comp = ['list','add','rename','time','remove']
return self.word_complete(text, comp)

def do_version(self, line):
self._do_timestamp(line, 'version')


#
# Milestone and Version share the same datastructure and methods:
#
def _do_timestamp(self, line, type):
arg = self.arg_tokenize(line)
try:
if arg[0] == 'list':
cmd = trac.admin_cmd.timestamp(self.envname, type)
data = cmd.list()
self.print_listing(['Name', 'Time'], data)
elif arg[0] == 'add' and len(arg) in [2,3]:
name = arg[1]
cmd = trac.admin_cmd.timestamp(self.envname, type)
data = cmd.add(name)
if len(arg) == 3:
time = arg[2]
data = cmd.time(name, time)
elif arg[0] == 'time' and len(arg)==3:
name = arg[1]
time = arg[2]
cmd = trac.admin_cmd.timestamp(self.envname, type)
cmd.time(name, time)
elif arg[0] == 'rename' and len(arg)==3:
name = arg[1]
newname = arg[2]
cmd = trac.admin_cmd.timestamp(self.envname, type)
cmd.rename(name, newname)
elif arg[0] == 'remove' and len(arg)==2:
name = arg[1]
cmd = trac.admin_cmd.timestamp(self.envname, type)
cmd.remove(name)
else:
self.do_help(type)
except Exception, e:
print 'Command %s failed:' % arg[0], e


#
# Quit / EOF
#
_help_quit = [['quit', 'Exit the program']]
_help_EOF = _help_quit

def do_quit(self,line):
print
sys.exit()

do_EOF = do_quit


## ---------------------------------------------------------------------------

##
## Main
##

def main():
tracadm = TracAdmin()
if len (sys.argv) > 1:
if sys.argv[1] in ['-h','--help','help']:
tracadm.docmd ("help")
elif sys.argv[1] in ['-v','--version','about']:
tracadm.docmd ("about")
else:
tracadm.env_set(sys.argv[1])
if len (sys.argv) > 2:
s_args = ' '.join(["'%s'" % c for c in sys.argv[3:]])
command = sys.argv[2] + ' ' +s_args
tracadm.docmd (command)
else:
while 1:
tracadm.run()
else:
tracadm.docmd ("help")


if __name__ == '__main__':
main()
Re: On-line admin [ In reply to ]
Tim,

I was waiting for someone with more knowledge of Python/Trac to give =
their opinion, but as I haven't seen anything on the list, my 2p worth =
says that apart from:

print >> sys.stderr, 'Unknown time format'

in admin_cmd.py, which should probably be passed up to trac-admin or the =
web interface instead, I don't see any issues.

Have you moved forward any further on this?

Ian.

-----Original Message-----
From: trac-bounces@lists.edgewall.com =
[mailto:trac-bounces@lists.edgewall.com] On Behalf Of Tim Moloney
Sent: 25 June 2004 17:18
To: trac@bobcat.edgewall.com
Subject: Re: [Trac] Re: On-line admin


Tim Moloney wrote:

> I have started working on pulling out the admin functions into a=20
> separate file for modularity. Conceptually, this isn't hard but it's=20
> taking me a little bit of time. I'm relatively new to Python so I=20
> still run into issues that I need to learn about.
>=20
> [...]
>=20
> Once I'm done (early next week?) I'll post my changes. If the code=20
> isn't too horrible, maybe it'll be accepted.

I have split the trac-admin functionality into trac-admin and =
admin_cmd.py. The idea is that trac-admin would only contain the text =
user interface and all of the actual administration is in admin_cmd.py. =
Then admin_cmd.py could be used by other user interfaces (web, gui, =
etc.).

Although this is mostly true, I have not yet moved the 'initenv' and =
'update' commands (and their support functions) to admin_cmd.py. =
Regardless, I think that enough is there for people to see if this is a =
good implementation (or if I should stop programming in Python =
altogether).

--=20
Tim Moloney
ManTech Real-time Systems Laboratory
2015 Cattlemen Road \ /
Sarasota, FL 34232 .________\(O)/________.
(941) 377-6775 x208 ' ' O(.)O ' '

---
Incoming mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.712 / Virus Database: 468 - Release Date: 27/06/2004
=20
=20
Re: On-line admin [ In reply to ]
Ian Leader wrote:

> I was waiting for someone with more knowledge of Python/Trac to
> give their opinion, but as I haven't seen anything on the list,
> my 2p worth says that apart from:
>
> print >> sys.stderr, 'Unknown time format'
>
> in admin_cmd.py, which should probably be passed up to trac-admin
> or the web interface instead, I don't see any issues.

Yes, there were several printf's that I missed. I have since either
changed them to exceptions or commented them out since they appeared
to be debug.

> Have you moved forward any further on this?

Yes, I've attached updated versions of trac-admin.py and adm_cmd.py.
(The attachment is a gzipped tar file to be more email friendly.)
The updated files include splitting the 'upgrade' and 'initenv'
commands. Splitting 'initenv' did change the text output as seen
by the user. I hope that this doesn't bother anyone.

--
Tim Moloney
ManTech Real-time Systems Laboratory
2015 Cattlemen Road \ /
Sarasota, FL 34232 .________\(O)/________.
(941) 377-6775 x208 ' ' O(.)O ' '
-------------- next part --------------
A non-text attachment was scrubbed...
Name: new_trac_admin.tgz
Type: application/x-gzip
Size: 11408 bytes
Desc: not available
Url : /archive/trac/attachments/20040630/38756702/new_trac_admin.bin