Skip to content

Commit a1776fa

Browse files
author
Richard Jones
committed
Added simple editing for classes that don't define a templated interface.
- access using the admin "class list" interface - limited to admin-only - requires the csv module from object-craft (url given if it's missing)
1 parent 2beffad commit a1776fa

File tree

4 files changed

+132
-9
lines changed

4 files changed

+132
-9
lines changed

CHANGES.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ Feature:
88
better configuration system.
99
. Alternate email addresses are now available for users. See the MIGRATION
1010
file for info on how to activate the feature.
11-
11+
. Added simple editing for classes that don't define a templated interface.
12+
- access using the admin "class list" interface
13+
- limited to admin-only
14+
- requires the csv module from object-craft (url given if it's missing)
1215

1316
Fixed:
1417
. Clean up mail handling, multipart handling.
@@ -25,6 +28,7 @@ Fixed:
2528
. #516854 ] "My Issues" and redisplay
2629
. #517906 ] Attribute order in "View customisation"
2730
. #514854 ] History: "User" is always ticket creator
31+
. wasn't handling cvs parser feeding correctly
2832

2933

3034
2002-01-24 - 0.4.0

roundup/cgi_client.py

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: cgi_client.py,v 1.102 2002-02-15 07:08:44 richard Exp $
18+
# $Id: cgi_client.py,v 1.103 2002-02-20 05:05:28 richard Exp $
1919

2020
__doc__ = """
2121
WWW request handler (also used in the stand-alone server).
@@ -298,10 +298,88 @@ def list(self, sort=None, group=None, filter=None, columns=None,
298298
show_customization = self.customization_widget()
299299

300300
index = htmltemplate.IndexTemplate(self, self.instance.TEMPLATES, cn)
301-
index.render(filterspec, filter, columns, sort, group,
302-
show_customization=show_customization)
301+
try:
302+
index.render(filterspec, filter, columns, sort, group,
303+
show_customization=show_customization)
304+
except htmltemplate.MissingTemplateError:
305+
self.basicClassEditPage()
303306
self.pagefoot()
304307

308+
def basicClassEditPage(self):
309+
'''Display a basic edit page that allows simple editing of the
310+
nodes of the current class
311+
'''
312+
if self.user != 'admin':
313+
raise Unauthorised
314+
w = self.write
315+
cn = self.classname
316+
cl = self.db.classes[cn]
317+
props = ['id'] + cl.getprops(protected=0).keys()
318+
319+
# get the CSV module
320+
try:
321+
import csv
322+
except ImportError:
323+
w(_('Sorry, you need the csv module to use this function.<br>\n'
324+
'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/'))
325+
return
326+
327+
# do the edit
328+
if self.form.has_key('rows'):
329+
rows = self.form['rows'].value.splitlines()
330+
p = csv.parser()
331+
idlessprops = props[1:]
332+
found = {}
333+
for row in rows:
334+
values = p.parse(row)
335+
# not a complete row, keep going
336+
if not values: continue
337+
338+
# extract the nodeid
339+
nodeid, values = values[0], values[1:]
340+
found[nodeid] = 1
341+
342+
# extract the new values
343+
d = {}
344+
for name, value in zip(idlessprops, values):
345+
d[name] = value.strip()
346+
347+
# perform the edit
348+
if cl.hasnode(nodeid):
349+
# edit existing
350+
cl.set(nodeid, **d)
351+
else:
352+
# new node
353+
found[cl.create(**d)] = 1
354+
355+
# retire the removed entries
356+
for nodeid in cl.list():
357+
if not found.has_key(nodeid):
358+
cl.retire(nodeid)
359+
360+
w(_('''<p class="form-help">You may edit the contents of the
361+
"%(classname)s" class using this form.</p>
362+
<p class="form-help">Remove entries by deleting their line. Add
363+
new entries by appending
364+
them to the table - put an X in the id column.</p>''')%{'classname':cn})
365+
366+
l = []
367+
for name in props:
368+
l.append(name)
369+
w('<tt>')
370+
w(', '.join(l) + '\n')
371+
w('</tt>')
372+
373+
w('<form onSubmit="return submit_once()" method="POST">')
374+
w('<textarea name="rows" cols=80 rows=15>')
375+
for nodeid in cl.list():
376+
l = []
377+
for name in props:
378+
l.append(cgi.escape(str(cl.get(nodeid, name))))
379+
w(', '.join(l) + '\n')
380+
381+
w(_('</textarea><br><input type="submit" value="Save Changes"></form>'))
382+
305383
def shownode(self, message=None):
306384
''' display an item
307385
'''
@@ -744,7 +822,8 @@ def classes(self, message=None):
744822
self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
745823
for cn in classnames:
746824
cl = self.db.getclass(cn)
747-
self.write('<tr class="list-header"><th colspan=2 align=left>%s</th></tr>'%cn.capitalize())
825+
self.write('<tr class="list-header"><th colspan=2 align=left>'
826+
'<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
748827
for key, value in cl.properties.items():
749828
if value is None: value = ''
750829
else: value = str(value)
@@ -1012,6 +1091,8 @@ def do_action(self, action, dre=re.compile(r'([^\d]+)(\d+)'),
10121091
if action == 'logout':
10131092
self.logout()
10141093
return
1094+
1095+
# see if we're to display an existing node
10151096
m = dre.match(action)
10161097
if m:
10171098
self.classname = m.group(1)
@@ -1030,6 +1111,8 @@ def do_action(self, action, dre=re.compile(r'([^\d]+)(\d+)'),
10301111
raise NotFound
10311112
func()
10321113
return
1114+
1115+
# see if we're to put up the new node page
10331116
m = nre.match(action)
10341117
if m:
10351118
self.classname = m.group(1)
@@ -1039,6 +1122,8 @@ def do_action(self, action, dre=re.compile(r'([^\d]+)(\d+)'),
10391122
raise NotFound
10401123
func()
10411124
return
1125+
1126+
# otherwise, display the named class
10421127
self.classname = action
10431128
try:
10441129
self.db.getclass(self.classname)
@@ -1202,6 +1287,10 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
12021287

12031288
#
12041289
# $Log: not supported by cvs2svn $
1290+
# Revision 1.102 2002/02/15 07:08:44 richard
1291+
# . Alternate email addresses are now available for users. See the MIGRATION
1292+
# file for info on how to activate the feature.
1293+
#
12051294
# Revision 1.101 2002/02/14 23:39:18 richard
12061295
# . All forms now have "double-submit" protection when Javascript is enabled
12071296
# on the client-side.

roundup/htmltemplate.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: htmltemplate.py,v 1.76 2002-02-16 09:10:52 richard Exp $
18+
# $Id: htmltemplate.py,v 1.77 2002-02-20 05:05:29 richard Exp $
1919

2020
__doc__ = """
2121
Template engine.
@@ -33,6 +33,9 @@
3333
except ImportError:
3434
StructuredText = None
3535

36+
class MissingTemplateError(ValueError):
37+
pass
38+
3639
class TemplateFunctions:
3740
def __init__(self):
3841
self.form = None
@@ -714,8 +717,12 @@ def render(self, filterspec={}, filter=[], columns=[], sort=[], group=[],
714717

715718
# XXX deviate from spec here ...
716719
# load the index section template and figure the default columns from it
717-
template = open(os.path.join(self.templates,
718-
self.classname+'.index')).read()
720+
try:
721+
template = open(os.path.join(self.templates,
722+
self.classname+'.index')).read()
723+
except IOError, error:
724+
if error.errno not in (errno.ENOENT, errno.ESRCH): raise
725+
raise MissingTemplateError, self.classname+'.index'
719726
all_columns = self.col_re.findall(template)
720727
if not columns:
721728
columns = []
@@ -1070,6 +1077,9 @@ def render(self, form):
10701077

10711078
#
10721079
# $Log: not supported by cvs2svn $
1080+
# Revision 1.76 2002/02/16 09:10:52 richard
1081+
# oops
1082+
#
10731083
# Revision 1.75 2002/02/16 08:43:23 richard
10741084
# . #517906 ] Attribute order in "View customisation"
10751085
#

roundup/hyperdb.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
1616
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
1717
#
18-
# $Id: hyperdb.py,v 1.55 2002-02-15 07:27:12 richard Exp $
18+
# $Id: hyperdb.py,v 1.56 2002-02-20 05:05:28 richard Exp $
1919

2020
__doc__ = """
2121
Hyperdatabase implementation, especially field types.
@@ -490,6 +490,7 @@ class or a KeyError is raised.
490490
if node.has_key(self.db.RETIRED_FLAG):
491491
raise IndexError
492492
num_re = re.compile('^\d+$')
493+
set = {}
493494
for key, value in propvalues.items():
494495
# check to make sure we're not duplicating an existing key
495496
if key == self.key and node[key] != value:
@@ -505,6 +506,13 @@ class or a KeyError is raised.
505506
# the writeable properties.
506507
prop = self.properties[key]
507508

509+
# if the value's the same as the existing value, no sense in
510+
# doing anything
511+
if value == node[key]:
512+
del propvalues[key]
513+
continue
514+
515+
# do stuff based on the prop type
508516
if isinstance(prop, Link):
509517
link_class = self.properties[key].classname
510518
# if it isn't a number, it's a key
@@ -598,6 +606,11 @@ class or a KeyError is raised.
598606

599607
node[key] = value
600608

609+
# nothing to do?
610+
if not propvalues:
611+
return
612+
613+
# do the set, and journal it
601614
self.db.setnode(self.classname, nodeid, node)
602615
self.db.addjournal(self.classname, nodeid, 'set', propvalues)
603616

@@ -633,6 +646,10 @@ def history(self, nodeid):
633646
return self.db.getjournal(self.classname, nodeid)
634647

635648
# Locating nodes:
649+
def hasnode(self, nodeid):
650+
'''Determine if the given nodeid actually exists
651+
'''
652+
return self.db.hasnode(self.classname, nodeid)
636653

637654
def setkey(self, propname):
638655
"""Select a String property of this class to be the key property.
@@ -1066,6 +1083,9 @@ def Choice(name, *options):
10661083

10671084
#
10681085
# $Log: not supported by cvs2svn $
1086+
# Revision 1.55 2002/02/15 07:27:12 richard
1087+
# Oops, precedences around the way w0rng.
1088+
#
10691089
# Revision 1.54 2002/02/15 07:08:44 richard
10701090
# . Alternate email addresses are now available for users. See the MIGRATION
10711091
# file for info on how to activate the feature.

0 commit comments

Comments
 (0)