Code

f39a72bc80daf69024f5a22d81069c1aed1fa7bf
[roundup.git] / roundup / htmltemplate.py
1 # $Id: htmltemplate.py,v 1.6 2001-07-29 04:06:42 richard Exp $
3 import os, re, StringIO, urllib, cgi, errno
5 import hyperdb, date
7 class Base:
8     def __init__(self, db, templates, classname, nodeid=None, form=None):
9         # TODO: really not happy with the way templates is passed on here
10         self.db, self.templates = db, templates
11         self.classname, self.nodeid = classname, nodeid
12         self.form = form
13         self.cl = self.db.classes[self.classname]
14         self.properties = self.cl.getprops()
16 class Plain(Base):
17     ''' display a String property directly;
19         display a Date property in a specified time zone with an option to
20         omit the time from the date stamp;
22         for a Link or Multilink property, display the key strings of the
23         linked nodes (or the ids if the linked class has no key property)
24     '''
25     def __call__(self, property):
26         if not self.nodeid and self.form is None:
27             return '[Field: not called from item]'
28         propclass = self.properties[property]
29         if self.nodeid:
30             value = self.cl.get(self.nodeid, property)
31         else:
32             # TODO: pull the value from the form
33             if propclass.isMultilinkType: value = []
34             else: value = ''
35         if propclass.isStringType:
36             if value is None: value = ''
37             else: value = str(value)
38         elif propclass.isDateType:
39             value = str(value)
40         elif propclass.isIntervalType:
41             value = str(value)
42         elif propclass.isLinkType:
43             linkcl = self.db.classes[propclass.classname]
44             k = linkcl.getkey()
45             # if the linked-to class doesn't have a key property, then try
46             # 'name', then 'title' and then just use a random one.
47             if not k:
48                 linkprops = linkcl.getprops()
49                 if linkprops.has_key('name'):
50                     k = 'name'
51                 elif linkprops.has_key('title'):
52                     k = 'title'
53                 else: 
54                     k = linkprops.keys()[0]
55             if value: value = str(linkcl.get(value, k))
56             else: value = '[unselected]'
57         elif propclass.isMultilinkType:
58             linkcl = self.db.classes[propclass.classname]
59             k = linkcl.getkey()
60             # if the linked-to class doesn't have a key property, then try
61             # 'name', then 'title' and then just use a random one.
62             if not k:
63                 linkprops = linkcl.getprops()
64                 if linkprops.has_key('name'):
65                     k = 'name'
66                 elif linkprops.has_key('title'):
67                     k = 'title'
68                 else: 
69                     k = linkprops.keys()[0]
70             value = ', '.join([linkcl.get(i, k) for i in value])
71         else:
72             s = 'Plain: bad propclass "%s"'%propclass
73         return value
75 class Field(Base):
76     ''' display a property like the plain displayer, but in a text field
77         to be edited
78     '''
79     def __call__(self, property, size=None, height=None, showid=0):
80         if not self.nodeid and self.form is None:
81             return '[Field: not called from item]'
82         propclass = self.properties[property]
83         if self.nodeid:
84             value = self.cl.get(self.nodeid, property)
85         else:
86             # TODO: pull the value from the form
87             if propclass.isMultilinkType: value = []
88             else: value = ''
89         if (propclass.isStringType or propclass.isDateType or
90                 propclass.isIntervalType):
91             size = size or 30
92             if value is None:
93                 value = ''
94             else:
95                 value = cgi.escape(value)
96                 value = '"'.join(value.split('"'))
97             s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
98         elif propclass.isLinkType:
99             linkcl = self.db.classes[propclass.classname]
100             l = ['<select name="%s">'%property]
101             k = linkcl.getkey()
102             # if the linked-to class doesn't have a key property, then try
103             # 'name', then 'title' and then just use a random one.
104             if not k:
105                 linkprops = linkcl.getprops()
106                 if linkprops.has_key('name'):
107                     k = 'name'
108                 elif linkprops.has_key('title'):
109                     k = 'title'
110                 else: 
111                     k = linkprops.keys()[0]
112             for optionid in linkcl.list():
113                 option = linkcl.get(optionid, k)
114                 s = ''
115                 if optionid == value:
116                     s = 'selected '
117                 if showid:
118                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
119                 else:
120                     lab = option
121                 if size is not None and len(lab) > size:
122                     lab = lab[:size-3] + '...'
123                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
124             l.append('</select>')
125             s = '\n'.join(l)
126         elif propclass.isMultilinkType:
127             linkcl = self.db.classes[propclass.classname]
128             list = linkcl.list()
129             height = height or min(len(list), 7)
130             l = ['<select multiple name="%s" size="%s">'%(property, height)]
131             k = linkcl.getkey()
132             # if the linked-to class doesn't have a key property, then try
133             # 'name', then 'title' and then just use a random one.
134             if not k:
135                 linkprops = linkcl.getprops()
136                 if linkprops.has_key('name'):
137                     k = 'name'
138                 elif linkprops.has_key('title'):
139                     k = 'title'
140                 else: 
141                     k = linkprops.keys()[0]
142             for optionid in list:
143                 option = linkcl.get(optionid, k)
144                 s = ''
145                 if optionid in value:
146                     s = 'selected '
147                 if showid:
148                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
149                 else:
150                     lab = option
151                 if size is not None and len(lab) > size:
152                     lab = lab[:size-3] + '...'
153                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
154             l.append('</select>')
155             s = '\n'.join(l)
156         else:
157             s = 'Plain: bad propclass "%s"'%propclass
158         return s
160 class Menu(Base):
161     ''' for a Link property, display a menu of the available choices
162     '''
163     def __call__(self, property, size=None, height=None, showid=0):
164         propclass = self.properties[property]
165         if self.nodeid:
166             value = self.cl.get(self.nodeid, property)
167         else:
168             # TODO: pull the value from the form
169             if propclass.isMultilinkType: value = []
170             else: value = None
171         if propclass.isLinkType:
172             linkcl = self.db.classes[propclass.classname]
173             l = ['<select name="%s">'%property]
174             k = linkcl.getkey()
175             # if the linked-to class doesn't have a key property, then try
176             # 'name', then 'title' and then just use a random one.
177             if not k:
178                 linkprops = linkcl.getprops()
179                 if linkprops.has_key('name'):
180                     k = 'name'
181                 elif linkprops.has_key('title'):
182                     k = 'title'
183                 else: 
184                     k = linkprops.keys()[0]
185             for optionid in linkcl.list():
186                 option = linkcl.get(optionid, k)
187                 s = ''
188                 if optionid == value:
189                     s = 'selected '
190                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
191             l.append('</select>')
192             return '\n'.join(l)
193         if propclass.isMultilinkType:
194             linkcl = self.db.classes[propclass.classname]
195             list = linkcl.list()
196             height = height or min(len(list), 7)
197             l = ['<select multiple name="%s" size="%s">'%(property, height)]
198             k = linkcl.getkey()
199             # if the linked-to class doesn't have a key property, then try
200             # 'name', then 'title' and then just use a random one.
201             if not k:
202                 linkprops = linkcl.getprops()
203                 if linkprops.has_key('name'):
204                     k = 'name'
205                 elif linkprops.has_key('title'):
206                     k = 'title'
207                 else: 
208                     k = linkprops.keys()[0]
209             for optionid in list:
210                 option = linkcl.get(optionid, k)
211                 s = ''
212                 if optionid in value:
213                     s = 'selected '
214                 if showid:
215                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
216                 else:
217                     lab = option
218                 if size is not None and len(lab) > size:
219                     lab = lab[:size-3] + '...'
220                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
221             l.append('</select>')
222             return '\n'.join(l)
223         return '[Menu: not a link]'
225 #XXX deviates from spec
226 class Link(Base):
227     ''' for a Link or Multilink property, display the names of the linked
228         nodes, hyperlinked to the item views on those nodes
229         for other properties, link to this node with the property as the text
230     '''
231     def __call__(self, property=None, **args):
232         if not self.nodeid and self.form is None:
233             return '[Link: not called from item]'
234         propclass = self.properties[property]
235         if self.nodeid:
236             value = self.cl.get(self.nodeid, property)
237         else:
238             if propclass.isMultilinkType: value = []
239             else: value = ''
240         if propclass.isLinkType:
241             if value is None:
242                 return '[not assigned]'
243             linkcl = self.db.classes[propclass.classname]
244             k = linkcl.getkey()
245             # if the linked-to class doesn't have a key property, then try
246             # 'name', then 'title' and then just use a random one.
247             if not k:
248                 linkprops = linkcl.getprops()
249                 if linkprops.has_key('name'):
250                     k = 'name'
251                 elif linkprops.has_key('title'):
252                     k = 'title'
253                 else: 
254                     k = linkprops.keys()[0]
255             linkvalue = linkcl.get(value, k)
256             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
257         if propclass.isMultilinkType:
258             linkcl = self.db.classes[propclass.classname]
259             k = linkcl.getkey()
260             # if the linked-to class doesn't have a key property, then try
261             # 'name', then 'title' and then just use a random one.
262             if not k:
263                 linkprops = linkcl.getprops()
264                 if linkprops.has_key('name'):
265                     k = 'name'
266                 elif linkprops.has_key('title'):
267                     k = 'title'
268                 else: 
269                     k = linkprops.keys()[0]
270             l = []
271             for value in value:
272                 linkvalue = linkcl.get(value, k)
273                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
274             return ', '.join(l)
275         return '<a href="%s%s">%s</a>'%(self.classname, self.nodeid, value)
277 class Count(Base):
278     ''' for a Multilink property, display a count of the number of links in
279         the list
280     '''
281     def __call__(self, property, **args):
282         if not self.nodeid:
283             return '[Count: not called from item]'
284         propclass = self.properties[property]
285         value = self.cl.get(self.nodeid, property)
286         if propclass.isMultilinkType:
287             return str(len(value))
288         return '[Count: not a Multilink]'
290 # XXX pretty is definitely new ;)
291 class Reldate(Base):
292     ''' display a Date property in terms of an interval relative to the
293         current date (e.g. "+ 3w", "- 2d").
295         with the 'pretty' flag, make it pretty
296     '''
297     def __call__(self, property, pretty=0):
298         if not self.nodeid and self.form is None:
299             return '[Reldate: not called from item]'
300         propclass = self.properties[property]
301         if not propclass.isDateType:
302             return '[Reldate: not a Date]'
303         if self.nodeid:
304             value = self.cl.get(self.nodeid, property)
305         else:
306             value = date.Date('.')
307         interval = value - date.Date('.')
308         if pretty:
309             if not self.nodeid:
310                 return 'now'
311             pretty = interval.pretty()
312             if pretty is None:
313                 pretty = value.pretty()
314             return pretty
315         return str(interval)
317 class Download(Base):
318     ''' show a Link("file") or Multilink("file") property using links that
319         allow you to download files
320     '''
321     def __call__(self, property, **args):
322         if not self.nodeid:
323             return '[Download: not called from item]'
324         propclass = self.properties[property]
325         value = self.cl.get(self.nodeid, property)
326         if propclass.isLinkType:
327             linkcl = self.db.classes[propclass.classname]
328             linkvalue = linkcl.get(value, k)
329             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
330         if propclass.isMultilinkType:
331             linkcl = self.db.classes[propclass.classname]
332             l = []
333             for value in value:
334                 linkvalue = linkcl.get(value, k)
335                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
336             return ', '.join(l)
337         return '[Download: not a link]'
340 class Checklist(Base):
341     ''' for a Link or Multilink property, display checkboxes for the available
342         choices to permit filtering
343     '''
344     def __call__(self, property, **args):
345         propclass = self.properties[property]
346         if self.nodeid:
347             value = self.cl.get(self.nodeid, property)
348         else:
349             value = []
350         if propclass.isLinkType or propclass.isMultilinkType:
351             linkcl = self.db.classes[propclass.classname]
352             l = []
353             k = linkcl.getkey()
354             # if the linked-to class doesn't have a key property, then try
355             # 'name', then 'title' and then just use a random one.
356             if not k:
357                 linkprops = linkcl.getprops()
358                 if linkprops.has_key('name'):
359                     k = 'name'
360                 elif linkprops.has_key('title'):
361                     k = 'title'
362                 else: 
363                     k = linkprops.keys()[0]
364             for optionid in linkcl.list():
365                 option = linkcl.get(optionid, k)
366                 if optionid in value:
367                     checked = 'checked'
368                 else:
369                     checked = ''
370                 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
371                     option, checked, propclass.classname, option))
372             return '\n'.join(l)
373         return '[Checklist: not a link]'
375 class Note(Base):
376     ''' display a "note" field, which is a text area for entering a note to
377         go along with a change. 
378     '''
379     def __call__(self, rows=5, cols=80):
380        # TODO: pull the value from the form
381         return '<textarea name="__note" rows=%s cols=%s></textarea>'%(rows,
382             cols)
384 # XXX new function
385 class List(Base):
386     ''' list the items specified by property using the standard index for
387         the class
388     '''
389     def __call__(self, property, **args):
390         propclass = self.properties[property]
391         if not propclass.isMultilinkType:
392             return '[List: not a Multilink]'
393         fp = StringIO.StringIO()
394         args['show_display_form'] = 0
395         value = self.cl.get(self.nodeid, property)
396         # TODO: really not happy with the way templates is passed on here
397         index(fp, self.templates, self.db, propclass.classname, nodeids=value,
398             show_display_form=0)
399         return fp.getvalue()
401 # XXX new function
402 class History(Base):
403     ''' list the history of the item
404     '''
405     def __call__(self, **args):
406         l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
407             '<tr class="list-header">',
408             '<td><span class="list-item"><strong>Date</strong></span></td>',
409             '<td><span class="list-item"><strong>User</strong></span></td>',
410             '<td><span class="list-item"><strong>Action</strong></span></td>',
411             '<td><span class="list-item"><strong>Args</strong></span></td>']
413         for id, date, user, action, args in self.cl.history(self.nodeid):
414             l.append('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'%(
415                date, user, action, args))
416         l.append('</table>')
417         return '\n'.join(l)
419 # XXX new function
420 class Submit(Base):
421     ''' add a submit button for the item
422     '''
423     def __call__(self):
424         if self.nodeid:
425             return '<input type="submit" value="Submit Changes">'
426         elif self.form is not None:
427             return '<input type="submit" value="Submit New Entry">'
428         else:
429             return '[Submit: not called from item]'
433 #   INDEX TEMPLATES
435 class IndexTemplateReplace:
436     def __init__(self, globals, locals, props):
437         self.globals = globals
438         self.locals = locals
439         self.props = props
441     def go(self, text, replace=re.compile(
442             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
443             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
444         return replace.sub(self, text)
445         
446     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
447         if m.group('name'):
448             if m.group('name') in self.props:
449                 text = m.group('text')
450                 replace = IndexTemplateReplace(self.globals, {}, self.props)
451                 return replace.go(m.group('text'))
452             else:
453                 return ''
454         if m.group('display'):
455             command = m.group('command')
456             return eval(command, self.globals, self.locals)
457         print '*** unhandled match', m.groupdict()
459 def sortby(sort_name, columns, filter, sort, group, filterspec):
460     l = []
461     w = l.append
462     for k, v in filterspec.items():
463         k = urllib.quote(k)
464         if type(v) == type([]):
465             w('%s=%s'%(k, ','.join(map(urllib.quote, v))))
466         else:
467             w('%s=%s'%(k, urllib.quote(v)))
468     if columns:
469         w(':columns=%s'%','.join(map(urllib.quote, columns)))
470     if filter:
471         w(':filter=%s'%','.join(map(urllib.quote, filter)))
472     if group:
473         w(':group=%s'%','.join(map(urllib.quote, group)))
474     m = []
475     s_dir = ''
476     for name in sort:
477         dir = name[0]
478         if dir == '-':
479             dir = ''
480         else:
481             name = name[1:]
482         if sort_name == name:
483             if dir == '':
484                 s_dir = '-'
485             elif dir == '-':
486                 s_dir = ''
487         else:
488             m.append(dir+urllib.quote(name))
489     m.insert(0, s_dir+urllib.quote(sort_name))
490     # so things don't get completely out of hand, limit the sort to two columns
491     w(':sort=%s'%','.join(m[:2]))
492     return '&'.join(l)
494 def index(client, templates, db, classname, filterspec={}, filter=[],
495         columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
496         col_re=re.compile(r'<property\s+name="([^>]+)">')):
497     globals = {
498         'plain': Plain(db, templates, classname, form={}),
499         'field': Field(db, templates, classname, form={}),
500         'menu': Menu(db, templates, classname, form={}),
501         'link': Link(db, templates, classname, form={}),
502         'count': Count(db, templates, classname, form={}),
503         'reldate': Reldate(db, templates, classname, form={}),
504         'download': Download(db, templates, classname, form={}),
505         'checklist': Checklist(db, templates, classname, form={}),
506         'list': List(db, templates, classname, form={}),
507         'history': History(db, templates, classname, form={}),
508         'submit': Submit(db, templates, classname, form={}),
509         'note': Note(db, templates, classname, form={})
510     }
511     cl = db.classes[classname]
512     properties = cl.getprops()
513     w = client.write
515     try:
516         template = open(os.path.join(templates, classname+'.filter')).read()
517         all_filters = col_re.findall(template)
518     except IOError, error:
519         if error.errno != errno.ENOENT: raise
520         template = None
521         all_filters = []
522     if template and filter:
523         # display the filter section
524         w('<form>')
525         w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
526         w('<tr class="location-bar">')
527         w(' <th align="left" colspan="2">Filter specification...</th>')
528         w('</tr>')
529         replace = IndexTemplateReplace(globals, locals(), filter)
530         w(replace.go(template))
531         if columns:
532             w('<input type="hidden" name=":columns" value="%s">'%','.join(columns))
533         if filter:
534             w('<input type="hidden" name=":filter" value="%s">'%','.join(filter))
535         if sort:
536             w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
537         if group:
538             w('<input type="hidden" name=":group" value="%s">'%','.join(group))
539         for k, v in filterspec.items():
540             if type(v) == type([]): v = ','.join(v)
541             w('<input type="hidden" name="%s" value="%s">'%(k, v))
542         w('<tr class="location-bar"><td width="1%%">&nbsp;</td>')
543         w('<td><input type="submit" value="Redisplay"></td></tr>')
544         w('</table>')
545         w('</form>')
547     # XXX deviate from spec here ...
548     # load the index section template and figure the default columns from it
549     template = open(os.path.join(templates, classname+'.index')).read()
550     all_columns = col_re.findall(template)
551     if not columns:
552         columns = []
553         for name in all_columns:
554             columns.append(name)
555     else:
556         # re-sort columns to be the same order as all_columns
557         l = []
558         for name in all_columns:
559             if name in columns:
560                 l.append(name)
561         columns = l
563     # now display the index section
564     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
565     w('<tr class="list-header">')
566     for name in columns:
567         cname = name.capitalize()
568         if show_display_form:
569             anchor = "%s?%s"%(classname, sortby(name, columns, filter,
570                 sort, group, filterspec))
571             w('<td><span class="list-item"><a href="%s">%s</a></span></td>'%(
572                 anchor, cname))
573         else:
574             w('<td><span class="list-item">%s</span></td>'%cname)
575     w('</tr>')
577     # this stuff is used for group headings - optimise the group names
578     old_group = None
579     group_names = []
580     if group:
581         for name in group:
582             if name[0] == '-': group_names.append(name[1:])
583             else: group_names.append(name)
585     # now actually loop through all the nodes we get from the filter and
586     # apply the template
587     if nodeids is None:
588         nodeids = cl.filter(filterspec, sort, group)
589     for nodeid in nodeids:
590         # check for a group heading
591         if group_names:
592             this_group = [cl.get(nodeid, name) for name in group_names]
593             if this_group != old_group:
594                 l = []
595                 for name in group_names:
596                     prop = properties[name]
597                     if prop.isLinkType:
598                         group_cl = db.classes[prop.classname]
599                         key = group_cl.getkey()
600                         value = cl.get(nodeid, name)
601                         if value is None:
602                             l.append('[unselected %s]'%prop.classname)
603                         else:
604                             l.append(group_cl.get(cl.get(nodeid, name), key))
605                     elif prop.isMultilinkType:
606                         group_cl = db.classes[prop.classname]
607                         key = group_cl.getkey()
608                         for value in cl.get(nodeid, name):
609                             l.append(group_cl.get(value, key))
610                     else:
611                         value = cl.get(nodeid, name)
612                         if value is None:
613                             value = '[empty %s]'%name
614                         l.append(value)
615                 w('<tr class="section-bar">'
616                   '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
617                     len(columns), ', '.join(l)))
618                 old_group = this_group
620         # display this node's row
621         for value in globals.values():
622             if hasattr(value, 'nodeid'):
623                 value.nodeid = nodeid
624         replace = IndexTemplateReplace(globals, locals(), columns)
625         w(replace.go(template))
627     w('</table>')
629     if not show_display_form:
630         return
632     # now add in the filter/columns/group/etc config table form
633     w('<p><form>')
634     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
635     for k,v in filterspec.items():
636         if type(v) == type([]): v = ','.join(v)
637         w('<input type="hidden" name="%s" value="%s">'%(k, v))
638     if sort:
639         w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
640     names = []
641     for name in cl.getprops().keys():
642         if name in all_filters or name in all_columns:
643             names.append(name)
644     w('<tr class="location-bar">')
645     w('<th align="left" colspan=%s>View customisation...</th></tr>'%
646         (len(names)+1))
647     w('<tr class="location-bar"><th>&nbsp;</th>')
648     for name in names:
649         w('<th>%s</th>'%name.capitalize())
650     w('</tr>')
652     # filter
653     if all_filters:
654         w('<tr><th width="1%" align=right class="location-bar">Filters</th>')
655         for name in names:
656             if name not in all_filters:
657                 w('<td>&nbsp;</td>')
658                 continue
659             if name in filter: checked=' checked'
660             else: checked=''
661             w('<td align=middle>')
662             w('<input type="checkbox" name=":filter" value="%s" %s></td>'%(name,
663                 checked))
664         w('</tr>')
666     # columns
667     if all_columns:
668         w('<tr><th width="1%" align=right class="location-bar">Columns</th>')
669         for name in names:
670             if name not in all_columns:
671                 w('<td>&nbsp;</td>')
672                 continue
673             if name in columns: checked=' checked'
674             else: checked=''
675             w('<td align=middle>')
676             w('<input type="checkbox" name=":columns" value="%s" %s></td>'%(
677                 name, checked))
678         w('</tr>')
680         # group
681         w('<tr><th width="1%" align=right class="location-bar">Grouping</th>')
682         for name in names:
683             prop = properties[name]
684             if name not in all_columns:
685                 w('<td>&nbsp;</td>')
686                 continue
687             if name in group: checked=' checked'
688             else: checked=''
689             w('<td align=middle>')
690             w('<input type="checkbox" name=":group" value="%s" %s></td>'%(
691                 name, checked))
692         w('</tr>')
694     w('<tr class="location-bar"><td width="1%">&nbsp;</td>')
695     w('<td colspan="%s">'%len(names))
696     w('<input type="submit" value="Redisplay"></td></tr>')
697     w('</table>')
698     w('</form>')
702 #   ITEM TEMPLATES
704 class ItemTemplateReplace:
705     def __init__(self, globals, locals, cl, nodeid):
706         self.globals = globals
707         self.locals = locals
708         self.cl = cl
709         self.nodeid = nodeid
711     def go(self, text, replace=re.compile(
712             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
713             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
714         return replace.sub(self, text)
716     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
717         if m.group('name'):
718             if self.nodeid and self.cl.get(self.nodeid, m.group('name')):
719                 replace = ItemTemplateReplace(self.globals, {}, self.cl,
720                     self.nodeid)
721                 return replace.go(m.group('text'))
722             else:
723                 return ''
724         if m.group('display'):
725             command = m.group('command')
726             return eval(command, self.globals, self.locals)
727         print '*** unhandled match', m.groupdict()
729 def item(client, templates, db, classname, nodeid, replace=re.compile(
730             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
731             r'(?P<endprop></property>)|'
732             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
734     globals = {
735         'plain': Plain(db, templates, classname, nodeid),
736         'field': Field(db, templates, classname, nodeid),
737         'menu': Menu(db, templates, classname, nodeid),
738         'link': Link(db, templates, classname, nodeid),
739         'count': Count(db, templates, classname, nodeid),
740         'reldate': Reldate(db, templates, classname, nodeid),
741         'download': Download(db, templates, classname, nodeid),
742         'checklist': Checklist(db, templates, classname, nodeid),
743         'list': List(db, templates, classname, nodeid),
744         'history': History(db, templates, classname, nodeid),
745         'submit': Submit(db, templates, classname, nodeid),
746         'note': Note(db, templates, classname, nodeid)
747     }
749     cl = db.classes[classname]
750     properties = cl.getprops()
752     if properties.has_key('type') and properties.has_key('content'):
753         pass
754         # XXX we really want to return this as a downloadable...
755         #  currently I handle this at a higher level by detecting 'file'
756         #  designators...
758     w = client.write
759     w('<form action="%s%s">'%(classname, nodeid))
760     s = open(os.path.join(templates, classname+'.item')).read()
761     replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
762     w(replace.go(s))
763     w('</form>')
766 def newitem(client, templates, db, classname, form, replace=re.compile(
767             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
768             r'(?P<endprop></property>)|'
769             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
770     globals = {
771         'plain': Plain(db, templates, classname, form=form),
772         'field': Field(db, templates, classname, form=form),
773         'menu': Menu(db, templates, classname, form=form),
774         'link': Link(db, templates, classname, form=form),
775         'count': Count(db, templates, classname, form=form),
776         'reldate': Reldate(db, templates, classname, form=form),
777         'download': Download(db, templates, classname, form=form),
778         'checklist': Checklist(db, templates, classname, form=form),
779         'list': List(db, templates, classname, form=form),
780         'history': History(db, templates, classname, form=form),
781         'submit': Submit(db, templates, classname, form=form),
782         'note': Note(db, templates, classname, form=form)
783     }
785     cl = db.classes[classname]
786     properties = cl.getprops()
788     w = client.write
789     try:
790         s = open(os.path.join(templates, classname+'.newitem')).read()
791     except:
792         s = open(os.path.join(templates, classname+'.item')).read()
793     w('<form action="new%s">'%classname)
794     replace = ItemTemplateReplace(globals, locals(), None, None)
795     w(replace.go(s))
796     w('</form>')
799 # $Log: not supported by cvs2svn $
800 # Revision 1.5  2001/07/28 08:17:09  richard
801 # fixed use of stylesheet
803 # Revision 1.4  2001/07/28 07:59:53  richard
804 # Replaced errno integers with their module values.
805 # De-tabbed templatebuilder.py
807 # Revision 1.3  2001/07/25 03:39:47  richard
808 # Hrm - displaying links to classes that don't specify a key property. I've
809 # got it defaulting to 'name', then 'title' and then a "random" property (first
810 # one returned by getprops().keys().
811 # Needs to be moved onto the Class I think...
813 # Revision 1.2  2001/07/22 12:09:32  richard
814 # Final commit of Grande Splite
816 # Revision 1.1  2001/07/22 11:58:35  richard
817 # More Grande Splite