Code

stupid typo
[roundup.git] / roundup / htmltemplate.py
1 #
2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10 # POSSIBILITY OF SUCH DAMAGE.
11 #
12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17
18 # $Id: htmltemplate.py,v 1.18 2001-08-07 00:24:42 richard Exp $
20 import os, re, StringIO, urllib, cgi, errno
22 import hyperdb, date
24 class Base:
25     def __init__(self, db, templates, classname, nodeid=None, form=None,
26             filterspec=None):
27         # TODO: really not happy with the way templates is passed on here
28         self.db, self.templates = db, templates
29         self.classname, self.nodeid = classname, nodeid
30         self.form, self.filterspec = form, filterspec
31         self.cl = self.db.classes[self.classname]
32         self.properties = self.cl.getprops()
34 class Plain(Base):
35     ''' display a String property directly;
37         display a Date property in a specified time zone with an option to
38         omit the time from the date stamp;
40         for a Link or Multilink property, display the key strings of the
41         linked nodes (or the ids if the linked class has no key property)
42     '''
43     def __call__(self, property):
44         if not self.nodeid and self.form is None:
45             return '[Field: not called from item]'
46         propclass = self.properties[property]
47         if self.nodeid:
48             value = self.cl.get(self.nodeid, property)
49         else:
50             # TODO: pull the value from the form
51             if propclass.isMultilinkType: value = []
52             else: value = ''
53         if propclass.isStringType:
54             if value is None: value = ''
55             else: value = str(value)
56         elif propclass.isDateType:
57             value = str(value)
58         elif propclass.isIntervalType:
59             value = str(value)
60         elif propclass.isLinkType:
61             linkcl = self.db.classes[propclass.classname]
62             k = linkcl.labelprop()
63             if value: value = str(linkcl.get(value, k))
64             else: value = '[unselected]'
65         elif propclass.isMultilinkType:
66             linkcl = self.db.classes[propclass.classname]
67             k = linkcl.labelprop()
68             value = ', '.join([linkcl.get(i, k) for i in value])
69         else:
70             s = 'Plain: bad propclass "%s"'%propclass
71         return value
73 class Field(Base):
74     ''' display a property like the plain displayer, but in a text field
75         to be edited
76     '''
77     def __call__(self, property, size=None, height=None, showid=0):
78         if not self.nodeid and self.form is None and self.filterspec is None:
79             return '[Field: not called from item]'
80         propclass = self.properties[property]
81         if self.nodeid:
82             value = self.cl.get(self.nodeid, property, None)
83             # TODO: remove this from the code ... it's only here for
84             # handling schema changes, and they should be handled outside
85             # of this code...
86             if propclass.isMultilinkType and value is None:
87                 value = []
88         elif self.filterspec is not None:
89             if propclass.isMultilinkType:
90                 value = self.filterspec.get(property, [])
91             else:
92                 value = self.filterspec.get(property, '')
93         else:
94             # TODO: pull the value from the form
95             if propclass.isMultilinkType: value = []
96             else: value = ''
97         if (propclass.isStringType or propclass.isDateType or
98                 propclass.isIntervalType):
99             size = size or 30
100             if value is None:
101                 value = ''
102             else:
103                 value = cgi.escape(value)
104                 value = '"'.join(value.split('"'))
105             s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
106         elif propclass.isLinkType:
107             linkcl = self.db.classes[propclass.classname]
108             l = ['<select name="%s">'%property]
109             k = linkcl.labelprop()
110             for optionid in linkcl.list():
111                 option = linkcl.get(optionid, k)
112                 s = ''
113                 if optionid == value:
114                     s = 'selected '
115                 if showid:
116                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
117                 else:
118                     lab = option
119                 if size is not None and len(lab) > size:
120                     lab = lab[:size-3] + '...'
121                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
122             l.append('</select>')
123             s = '\n'.join(l)
124         elif propclass.isMultilinkType:
125             linkcl = self.db.classes[propclass.classname]
126             list = linkcl.list()
127             height = height or min(len(list), 7)
128             l = ['<select multiple name="%s" size="%s">'%(property, height)]
129             k = linkcl.labelprop()
130             for optionid in list:
131                 option = linkcl.get(optionid, k)
132                 s = ''
133                 if optionid in value:
134                     s = 'selected '
135                 if showid:
136                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
137                 else:
138                     lab = option
139                 if size is not None and len(lab) > size:
140                     lab = lab[:size-3] + '...'
141                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
142             l.append('</select>')
143             s = '\n'.join(l)
144         else:
145             s = 'Plain: bad propclass "%s"'%propclass
146         return s
148 class Menu(Base):
149     ''' for a Link property, display a menu of the available choices
150     '''
151     def __call__(self, property, size=None, height=None, showid=0):
152         propclass = self.properties[property]
153         if self.nodeid:
154             value = self.cl.get(self.nodeid, property)
155         else:
156             # TODO: pull the value from the form
157             if propclass.isMultilinkType: value = []
158             else: value = None
159         if propclass.isLinkType:
160             linkcl = self.db.classes[propclass.classname]
161             l = ['<select name="%s">'%property]
162             k = linkcl.labelprop()
163             for optionid in linkcl.list():
164                 option = linkcl.get(optionid, k)
165                 s = ''
166                 if optionid == value:
167                     s = 'selected '
168                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
169             l.append('</select>')
170             return '\n'.join(l)
171         if propclass.isMultilinkType:
172             linkcl = self.db.classes[propclass.classname]
173             list = linkcl.list()
174             height = height or min(len(list), 7)
175             l = ['<select multiple name="%s" size="%s">'%(property, height)]
176             k = linkcl.labelprop()
177             for optionid in list:
178                 option = linkcl.get(optionid, k)
179                 s = ''
180                 if optionid in value:
181                     s = 'selected '
182                 if showid:
183                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
184                 else:
185                     lab = option
186                 if size is not None and len(lab) > size:
187                     lab = lab[:size-3] + '...'
188                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
189             l.append('</select>')
190             return '\n'.join(l)
191         return '[Menu: not a link]'
193 #XXX deviates from spec
194 class Link(Base):
195     ''' for a Link or Multilink property, display the names of the linked
196         nodes, hyperlinked to the item views on those nodes
197         for other properties, link to this node with the property as the text
198     '''
199     def __call__(self, property=None, **args):
200         if not self.nodeid and self.form is None:
201             return '[Link: not called from item]'
202         propclass = self.properties[property]
203         if self.nodeid:
204             value = self.cl.get(self.nodeid, property)
205         else:
206             if propclass.isMultilinkType: value = []
207             else: value = ''
208         if propclass.isLinkType:
209             if value is None:
210                 return '[not assigned]'
211             linkcl = self.db.classes[propclass.classname]
212             k = linkcl.labelprop()
213             linkvalue = linkcl.get(value, k)
214             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
215         if propclass.isMultilinkType:
216             linkcl = self.db.classes[propclass.classname]
217             k = linkcl.labelprop()
218             l = []
219             for value in value:
220                 linkvalue = linkcl.get(value, k)
221                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
222             return ', '.join(l)
223         return '<a href="%s%s">%s</a>'%(self.classname, self.nodeid, value)
225 class Count(Base):
226     ''' for a Multilink property, display a count of the number of links in
227         the list
228     '''
229     def __call__(self, property, **args):
230         if not self.nodeid:
231             return '[Count: not called from item]'
232         propclass = self.properties[property]
233         value = self.cl.get(self.nodeid, property)
234         if propclass.isMultilinkType:
235             return str(len(value))
236         return '[Count: not a Multilink]'
238 # XXX pretty is definitely new ;)
239 class Reldate(Base):
240     ''' display a Date property in terms of an interval relative to the
241         current date (e.g. "+ 3w", "- 2d").
243         with the 'pretty' flag, make it pretty
244     '''
245     def __call__(self, property, pretty=0):
246         if not self.nodeid and self.form is None:
247             return '[Reldate: not called from item]'
248         propclass = self.properties[property]
249         if not propclass.isDateType:
250             return '[Reldate: not a Date]'
251         if self.nodeid:
252             value = self.cl.get(self.nodeid, property)
253         else:
254             value = date.Date('.')
255         interval = value - date.Date('.')
256         if pretty:
257             if not self.nodeid:
258                 return 'now'
259             pretty = interval.pretty()
260             if pretty is None:
261                 pretty = value.pretty()
262             return pretty
263         return str(interval)
265 class Download(Base):
266     ''' show a Link("file") or Multilink("file") property using links that
267         allow you to download files
268     '''
269     def __call__(self, property, **args):
270         if not self.nodeid:
271             return '[Download: not called from item]'
272         propclass = self.properties[property]
273         value = self.cl.get(self.nodeid, property)
274         if propclass.isLinkType:
275             linkcl = self.db.classes[propclass.classname]
276             linkvalue = linkcl.get(value, k)
277             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
278         if propclass.isMultilinkType:
279             linkcl = self.db.classes[propclass.classname]
280             l = []
281             for value in value:
282                 linkvalue = linkcl.get(value, k)
283                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
284             return ', '.join(l)
285         return '[Download: not a link]'
288 class Checklist(Base):
289     ''' for a Link or Multilink property, display checkboxes for the available
290         choices to permit filtering
291     '''
292     def __call__(self, property, **args):
293         propclass = self.properties[property]
294         if self.nodeid:
295             value = self.cl.get(self.nodeid, property)
296         elif self.filterspec is not None:
297             value = self.filterspec.get(property, [])
298         else:
299             value = []
300         if propclass.isLinkType or propclass.isMultilinkType:
301             linkcl = self.db.classes[propclass.classname]
302             l = []
303             k = linkcl.labelprop()
304             for optionid in linkcl.list():
305                 option = linkcl.get(optionid, k)
306                 if optionid in value or option in value:
307                     checked = 'checked'
308                 else:
309                     checked = ''
310                 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
311                     option, checked, property, option))
312             return '\n'.join(l)
313         return '[Checklist: not a link]'
315 class Note(Base):
316     ''' display a "note" field, which is a text area for entering a note to
317         go along with a change. 
318     '''
319     def __call__(self, rows=5, cols=80):
320        # TODO: pull the value from the form
321         return '<textarea name="__note" rows=%s cols=%s></textarea>'%(rows,
322             cols)
324 # XXX new function
325 class List(Base):
326     ''' list the items specified by property using the standard index for
327         the class
328     '''
329     def __call__(self, property, **args):
330         propclass = self.properties[property]
331         if not propclass.isMultilinkType:
332             return '[List: not a Multilink]'
333         fp = StringIO.StringIO()
334         args['show_display_form'] = 0
335         value = self.cl.get(self.nodeid, property)
336         # TODO: really not happy with the way templates is passed on here
337         index(fp, self.templates, self.db, propclass.classname, nodeids=value,
338             show_display_form=0)
339         return fp.getvalue()
341 # XXX new function
342 class History(Base):
343     ''' list the history of the item
344     '''
345     def __call__(self, **args):
346         if self.nodeid is None:
347             return "[History: node doesn't exist]"
349         l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
350             '<tr class="list-header">',
351             '<td><span class="list-item"><strong>Date</strong></span></td>',
352             '<td><span class="list-item"><strong>User</strong></span></td>',
353             '<td><span class="list-item"><strong>Action</strong></span></td>',
354             '<td><span class="list-item"><strong>Args</strong></span></td>']
356         for id, date, user, action, args in self.cl.history(self.nodeid):
357             l.append('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'%(
358                date, user, action, args))
359         l.append('</table>')
360         return '\n'.join(l)
362 # XXX new function
363 class Submit(Base):
364     ''' add a submit button for the item
365     '''
366     def __call__(self):
367         if self.nodeid:
368             return '<input type="submit" value="Submit Changes">'
369         elif self.form is not None:
370             return '<input type="submit" value="Submit New Entry">'
371         else:
372             return '[Submit: not called from item]'
376 #   INDEX TEMPLATES
378 class IndexTemplateReplace:
379     def __init__(self, globals, locals, props):
380         self.globals = globals
381         self.locals = locals
382         self.props = props
384     def go(self, text, replace=re.compile(
385             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
386             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
387         return replace.sub(self, text)
388         
389     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
390         if m.group('name'):
391             if m.group('name') in self.props:
392                 text = m.group('text')
393                 replace = IndexTemplateReplace(self.globals, {}, self.props)
394                 return replace.go(m.group('text'))
395             else:
396                 return ''
397         if m.group('display'):
398             command = m.group('command')
399             return eval(command, self.globals, self.locals)
400         print '*** unhandled match', m.groupdict()
402 def sortby(sort_name, columns, filter, sort, group, filterspec):
403     l = []
404     w = l.append
405     for k, v in filterspec.items():
406         k = urllib.quote(k)
407         if type(v) == type([]):
408             w('%s=%s'%(k, ','.join(map(urllib.quote, v))))
409         else:
410             w('%s=%s'%(k, urllib.quote(v)))
411     if columns:
412         w(':columns=%s'%','.join(map(urllib.quote, columns)))
413     if filter:
414         w(':filter=%s'%','.join(map(urllib.quote, filter)))
415     if group:
416         w(':group=%s'%','.join(map(urllib.quote, group)))
417     m = []
418     s_dir = ''
419     for name in sort:
420         dir = name[0]
421         if dir == '-':
422             name = name[1:]
423         else:
424             dir = ''
425         if sort_name == name:
426             if dir == '-':
427                 s_dir = ''
428             else:
429                 s_dir = '-'
430         else:
431             m.append(dir+urllib.quote(name))
432     m.insert(0, s_dir+urllib.quote(sort_name))
433     # so things don't get completely out of hand, limit the sort to two columns
434     w(':sort=%s'%','.join(m[:2]))
435     return '&'.join(l)
437 def index(client, templates, db, classname, filterspec={}, filter=[],
438         columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
439         col_re=re.compile(r'<property\s+name="([^>]+)">')):
440     globals = {
441         'plain': Plain(db, templates, classname, filterspec=filterspec),
442         'field': Field(db, templates, classname, filterspec=filterspec),
443         'menu': Menu(db, templates, classname, filterspec=filterspec),
444         'link': Link(db, templates, classname, filterspec=filterspec),
445         'count': Count(db, templates, classname, filterspec=filterspec),
446         'reldate': Reldate(db, templates, classname, filterspec=filterspec),
447         'download': Download(db, templates, classname, filterspec=filterspec),
448         'checklist': Checklist(db, templates, classname, filterspec=filterspec),
449         'list': List(db, templates, classname, filterspec=filterspec),
450         'history': History(db, templates, classname, filterspec=filterspec),
451         'submit': Submit(db, templates, classname, filterspec=filterspec),
452         'note': Note(db, templates, classname, filterspec=filterspec)
453     }
454     cl = db.classes[classname]
455     properties = cl.getprops()
456     w = client.write
457     w('<form>')
459     try:
460         template = open(os.path.join(templates, classname+'.filter')).read()
461         all_filters = col_re.findall(template)
462     except IOError, error:
463         if error.errno != errno.ENOENT: raise
464         template = None
465         all_filters = []
466     if template and filter:
467         # display the filter section
468         w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
469         w('<tr class="location-bar">')
470         w(' <th align="left" colspan="2">Filter specification...</th>')
471         w('</tr>')
472         replace = IndexTemplateReplace(globals, locals(), filter)
473         w(replace.go(template))
474         w('<tr class="location-bar"><td width="1%%">&nbsp;</td>')
475         w('<td><input type="submit" value="Redisplay"></td></tr>')
476         w('</table>')
478     # If the filters aren't being displayed, then hide their current
479     # value in the form
480     if not filter:
481         for k, v in filterspec.items():
482             if type(v) == type([]): v = ','.join(v)
483             w('<input type="hidden" name="%s" value="%s">'%(k, v))
485     # make sure that the sorting doesn't get lost either
486     if sort:
487         w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
489     # XXX deviate from spec here ...
490     # load the index section template and figure the default columns from it
491     template = open(os.path.join(templates, classname+'.index')).read()
492     all_columns = col_re.findall(template)
493     if not columns:
494         columns = []
495         for name in all_columns:
496             columns.append(name)
497     else:
498         # re-sort columns to be the same order as all_columns
499         l = []
500         for name in all_columns:
501             if name in columns:
502                 l.append(name)
503         columns = l
505     # now display the index section
506     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
507     w('<tr class="list-header">')
508     for name in columns:
509         cname = name.capitalize()
510         if show_display_form:
511             anchor = "%s?%s"%(classname, sortby(name, columns, filter,
512                 sort, group, filterspec))
513             w('<td><span class="list-item"><a href="%s">%s</a></span></td>'%(
514                 anchor, cname))
515         else:
516             w('<td><span class="list-item">%s</span></td>'%cname)
517     w('</tr>')
519     # this stuff is used for group headings - optimise the group names
520     old_group = None
521     group_names = []
522     if group:
523         for name in group:
524             if name[0] == '-': group_names.append(name[1:])
525             else: group_names.append(name)
527     # now actually loop through all the nodes we get from the filter and
528     # apply the template
529     if nodeids is None:
530         nodeids = cl.filter(filterspec, sort, group)
531     for nodeid in nodeids:
532         # check for a group heading
533         if group_names:
534             this_group = [cl.get(nodeid, name) for name in group_names]
535             if this_group != old_group:
536                 l = []
537                 for name in group_names:
538                     prop = properties[name]
539                     if prop.isLinkType:
540                         group_cl = db.classes[prop.classname]
541                         key = group_cl.getkey()
542                         value = cl.get(nodeid, name)
543                         if value is None:
544                             l.append('[unselected %s]'%prop.classname)
545                         else:
546                             l.append(group_cl.get(cl.get(nodeid, name), key))
547                     elif prop.isMultilinkType:
548                         group_cl = db.classes[prop.classname]
549                         key = group_cl.getkey()
550                         for value in cl.get(nodeid, name):
551                             l.append(group_cl.get(value, key))
552                     else:
553                         value = cl.get(nodeid, name)
554                         if value is None:
555                             value = '[empty %s]'%name
556                         l.append(value)
557                 w('<tr class="section-bar">'
558                   '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
559                     len(columns), ', '.join(l)))
560                 old_group = this_group
562         # display this node's row
563         for value in globals.values():
564             if hasattr(value, 'nodeid'):
565                 value.nodeid = nodeid
566         replace = IndexTemplateReplace(globals, locals(), columns)
567         w(replace.go(template))
569     w('</table>')
571     if not show_display_form:
572         return
574     # now add in the filter/columns/group/etc config table form
575     w('<p>')
576     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
577     names = []
578     for name in cl.getprops().keys():
579         if name in all_filters or name in all_columns:
580             names.append(name)
581     w('<tr class="location-bar">')
582     w('<th align="left" colspan=%s>View customisation...</th></tr>'%
583         (len(names)+1))
584     w('<tr class="location-bar"><th>&nbsp;</th>')
585     for name in names:
586         w('<th>%s</th>'%name.capitalize())
587     w('</tr>')
589     # filter
590     if all_filters:
591         w('<tr><th width="1%" align=right class="location-bar">Filters</th>')
592         for name in names:
593             if name not in all_filters:
594                 w('<td>&nbsp;</td>')
595                 continue
596             if name in filter: checked=' checked'
597             else: checked=''
598             w('<td align=middle>')
599             w('<input type="checkbox" name=":filter" value="%s" %s></td>'%(name,
600                 checked))
601         w('</tr>')
603     # columns
604     if all_columns:
605         w('<tr><th width="1%" align=right class="location-bar">Columns</th>')
606         for name in names:
607             if name not in all_columns:
608                 w('<td>&nbsp;</td>')
609                 continue
610             if name in columns: checked=' checked'
611             else: checked=''
612             w('<td align=middle>')
613             w('<input type="checkbox" name=":columns" value="%s" %s></td>'%(
614                 name, checked))
615         w('</tr>')
617         # group
618         w('<tr><th width="1%" align=right class="location-bar">Grouping</th>')
619         for name in names:
620             prop = properties[name]
621             if name not in all_columns:
622                 w('<td>&nbsp;</td>')
623                 continue
624             if name in group: checked=' checked'
625             else: checked=''
626             w('<td align=middle>')
627             w('<input type="checkbox" name=":group" value="%s" %s></td>'%(
628                 name, checked))
629         w('</tr>')
631     w('<tr class="location-bar"><td width="1%">&nbsp;</td>')
632     w('<td colspan="%s">'%len(names))
633     w('<input type="submit" value="Redisplay"></td></tr>')
634     w('</table>')
635     w('</form>')
639 #   ITEM TEMPLATES
641 class ItemTemplateReplace:
642     def __init__(self, globals, locals, cl, nodeid):
643         self.globals = globals
644         self.locals = locals
645         self.cl = cl
646         self.nodeid = nodeid
648     def go(self, text, replace=re.compile(
649             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
650             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
651         return replace.sub(self, text)
653     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
654         if m.group('name'):
655             if self.nodeid and self.cl.get(self.nodeid, m.group('name')):
656                 replace = ItemTemplateReplace(self.globals, {}, self.cl,
657                     self.nodeid)
658                 return replace.go(m.group('text'))
659             else:
660                 return ''
661         if m.group('display'):
662             command = m.group('command')
663             return eval(command, self.globals, self.locals)
664         print '*** unhandled match', m.groupdict()
666 def item(client, templates, db, classname, nodeid, replace=re.compile(
667             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
668             r'(?P<endprop></property>)|'
669             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
671     globals = {
672         'plain': Plain(db, templates, classname, nodeid),
673         'field': Field(db, templates, classname, nodeid),
674         'menu': Menu(db, templates, classname, nodeid),
675         'link': Link(db, templates, classname, nodeid),
676         'count': Count(db, templates, classname, nodeid),
677         'reldate': Reldate(db, templates, classname, nodeid),
678         'download': Download(db, templates, classname, nodeid),
679         'checklist': Checklist(db, templates, classname, nodeid),
680         'list': List(db, templates, classname, nodeid),
681         'history': History(db, templates, classname, nodeid),
682         'submit': Submit(db, templates, classname, nodeid),
683         'note': Note(db, templates, classname, nodeid)
684     }
686     cl = db.classes[classname]
687     properties = cl.getprops()
689     if properties.has_key('type') and properties.has_key('content'):
690         pass
691         # XXX we really want to return this as a downloadable...
692         #  currently I handle this at a higher level by detecting 'file'
693         #  designators...
695     w = client.write
696     w('<form action="%s%s">'%(classname, nodeid))
697     s = open(os.path.join(templates, classname+'.item')).read()
698     replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
699     w(replace.go(s))
700     w('</form>')
703 def newitem(client, templates, db, classname, form, replace=re.compile(
704             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
705             r'(?P<endprop></property>)|'
706             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
707     globals = {
708         'plain': Plain(db, templates, classname, form=form),
709         'field': Field(db, templates, classname, form=form),
710         'menu': Menu(db, templates, classname, form=form),
711         'link': Link(db, templates, classname, form=form),
712         'count': Count(db, templates, classname, form=form),
713         'reldate': Reldate(db, templates, classname, form=form),
714         'download': Download(db, templates, classname, form=form),
715         'checklist': Checklist(db, templates, classname, form=form),
716         'list': List(db, templates, classname, form=form),
717         'history': History(db, templates, classname, form=form),
718         'submit': Submit(db, templates, classname, form=form),
719         'note': Note(db, templates, classname, form=form)
720     }
722     cl = db.classes[classname]
723     properties = cl.getprops()
725     w = client.write
726     try:
727         s = open(os.path.join(templates, classname+'.newitem')).read()
728     except:
729         s = open(os.path.join(templates, classname+'.item')).read()
730     w('<form action="new%s" method="POST" enctype="multipart/form-data">'%classname)
731     for key in form.keys():
732         if key[0] == ':':
733             value = form[key].value
734             if type(value) != type([]): value = [value]
735             for value in value:
736                 w('<input type="hidden" name="%s" value="%s">'%(key, value))
737     replace = ItemTemplateReplace(globals, locals(), None, None)
738     w(replace.go(s))
739     w('</form>')
742 # $Log: not supported by cvs2svn $
743 # Revision 1.17  2001/08/07 00:15:51  richard
744 # Added the copyright/license notice to (nearly) all files at request of
745 # Bizar Software.
747 # Revision 1.16  2001/08/01 03:52:23  richard
748 # Checklist was using wrong name.
750 # Revision 1.15  2001/07/30 08:12:17  richard
751 # Added time logging and file uploading to the templates.
753 # Revision 1.14  2001/07/30 06:17:45  richard
754 # Features:
755 #  . Added ability for cgi newblah forms to indicate that the new node
756 #    should be linked somewhere.
757 # Fixed:
758 #  . Fixed the agument handling for the roundup-admin find command.
759 #  . Fixed handling of summary when no note supplied for newblah. Again.
760 #  . Fixed detection of no form in htmltemplate Field display.
762 # Revision 1.13  2001/07/30 02:37:53  richard
763 # Temporary measure until we have decent schema migration.
765 # Revision 1.12  2001/07/30 01:24:33  richard
766 # Handles new node display now.
768 # Revision 1.11  2001/07/29 09:31:35  richard
769 # oops
771 # Revision 1.10  2001/07/29 09:28:23  richard
772 # Fixed sorting by clicking on column headings.
774 # Revision 1.9  2001/07/29 08:27:40  richard
775 # Fixed handling of passed-in values in form elements (ie. during a
776 # drill-down)
778 # Revision 1.8  2001/07/29 07:01:39  richard
779 # Added vim command to all source so that we don't get no steenkin' tabs :)
781 # Revision 1.7  2001/07/29 05:36:14  richard
782 # Cleanup of the link label generation.
784 # Revision 1.6  2001/07/29 04:06:42  richard
785 # Fixed problem in link display when Link value is None.
787 # Revision 1.5  2001/07/28 08:17:09  richard
788 # fixed use of stylesheet
790 # Revision 1.4  2001/07/28 07:59:53  richard
791 # Replaced errno integers with their module values.
792 # De-tabbed templatebuilder.py
794 # Revision 1.3  2001/07/25 03:39:47  richard
795 # Hrm - displaying links to classes that don't specify a key property. I've
796 # got it defaulting to 'name', then 'title' and then a "random" property (first
797 # one returned by getprops().keys().
798 # Needs to be moved onto the Class I think...
800 # Revision 1.2  2001/07/22 12:09:32  richard
801 # Final commit of Grande Splite
803 # Revision 1.1  2001/07/22 11:58:35  richard
804 # More Grande Splite
807 # vim: set filetype=python ts=4 sw=4 et si