Code

c7075b3150dd3360a848028afdd81c348cc92fa0
[roundup.git] / roundup / htmltemplate.py
1 # $Id: htmltemplate.py,v 1.5 2001-07-28 08:17:09 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             linkcl = self.db.classes[propclass.classname]
242             k = linkcl.getkey()
243             # if the linked-to class doesn't have a key property, then try
244             # 'name', then 'title' and then just use a random one.
245             if not k:
246                 linkprops = linkcl.getprops()
247                 if linkprops.has_key('name'):
248                     k = 'name'
249                 elif linkprops.has_key('title'):
250                     k = 'title'
251                 else: 
252                     k = linkprops.keys()[0]
253             linkvalue = linkcl.get(value, k)
254             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
255         if propclass.isMultilinkType:
256             linkcl = self.db.classes[propclass.classname]
257             k = linkcl.getkey()
258             # if the linked-to class doesn't have a key property, then try
259             # 'name', then 'title' and then just use a random one.
260             if not k:
261                 linkprops = linkcl.getprops()
262                 if linkprops.has_key('name'):
263                     k = 'name'
264                 elif linkprops.has_key('title'):
265                     k = 'title'
266                 else: 
267                     k = linkprops.keys()[0]
268             l = []
269             for value in value:
270                 linkvalue = linkcl.get(value, k)
271                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
272             return ', '.join(l)
273         return '<a href="%s%s">%s</a>'%(self.classname, self.nodeid, value)
275 class Count(Base):
276     ''' for a Multilink property, display a count of the number of links in
277         the list
278     '''
279     def __call__(self, property, **args):
280         if not self.nodeid:
281             return '[Count: not called from item]'
282         propclass = self.properties[property]
283         value = self.cl.get(self.nodeid, property)
284         if propclass.isMultilinkType:
285             return str(len(value))
286         return '[Count: not a Multilink]'
288 # XXX pretty is definitely new ;)
289 class Reldate(Base):
290     ''' display a Date property in terms of an interval relative to the
291         current date (e.g. "+ 3w", "- 2d").
293         with the 'pretty' flag, make it pretty
294     '''
295     def __call__(self, property, pretty=0):
296         if not self.nodeid and self.form is None:
297             return '[Reldate: not called from item]'
298         propclass = self.properties[property]
299         if not propclass.isDateType:
300             return '[Reldate: not a Date]'
301         if self.nodeid:
302             value = self.cl.get(self.nodeid, property)
303         else:
304             value = date.Date('.')
305         interval = value - date.Date('.')
306         if pretty:
307             if not self.nodeid:
308                 return 'now'
309             pretty = interval.pretty()
310             if pretty is None:
311                 pretty = value.pretty()
312             return pretty
313         return str(interval)
315 class Download(Base):
316     ''' show a Link("file") or Multilink("file") property using links that
317         allow you to download files
318     '''
319     def __call__(self, property, **args):
320         if not self.nodeid:
321             return '[Download: not called from item]'
322         propclass = self.properties[property]
323         value = self.cl.get(self.nodeid, property)
324         if propclass.isLinkType:
325             linkcl = self.db.classes[propclass.classname]
326             linkvalue = linkcl.get(value, k)
327             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
328         if propclass.isMultilinkType:
329             linkcl = self.db.classes[propclass.classname]
330             l = []
331             for value in value:
332                 linkvalue = linkcl.get(value, k)
333                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
334             return ', '.join(l)
335         return '[Download: not a link]'
338 class Checklist(Base):
339     ''' for a Link or Multilink property, display checkboxes for the available
340         choices to permit filtering
341     '''
342     def __call__(self, property, **args):
343         propclass = self.properties[property]
344         if self.nodeid:
345             value = self.cl.get(self.nodeid, property)
346         else:
347             value = []
348         if propclass.isLinkType or propclass.isMultilinkType:
349             linkcl = self.db.classes[propclass.classname]
350             l = []
351             k = linkcl.getkey()
352             # if the linked-to class doesn't have a key property, then try
353             # 'name', then 'title' and then just use a random one.
354             if not k:
355                 linkprops = linkcl.getprops()
356                 if linkprops.has_key('name'):
357                     k = 'name'
358                 elif linkprops.has_key('title'):
359                     k = 'title'
360                 else: 
361                     k = linkprops.keys()[0]
362             for optionid in linkcl.list():
363                 option = linkcl.get(optionid, k)
364                 if optionid in value:
365                     checked = 'checked'
366                 else:
367                     checked = ''
368                 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
369                     option, checked, propclass.classname, option))
370             return '\n'.join(l)
371         return '[Checklist: not a link]'
373 class Note(Base):
374     ''' display a "note" field, which is a text area for entering a note to
375         go along with a change. 
376     '''
377     def __call__(self, rows=5, cols=80):
378        # TODO: pull the value from the form
379         return '<textarea name="__note" rows=%s cols=%s></textarea>'%(rows,
380             cols)
382 # XXX new function
383 class List(Base):
384     ''' list the items specified by property using the standard index for
385         the class
386     '''
387     def __call__(self, property, **args):
388         propclass = self.properties[property]
389         if not propclass.isMultilinkType:
390             return '[List: not a Multilink]'
391         fp = StringIO.StringIO()
392         args['show_display_form'] = 0
393         value = self.cl.get(self.nodeid, property)
394         # TODO: really not happy with the way templates is passed on here
395         index(fp, self.templates, self.db, propclass.classname, nodeids=value,
396             show_display_form=0)
397         return fp.getvalue()
399 # XXX new function
400 class History(Base):
401     ''' list the history of the item
402     '''
403     def __call__(self, **args):
404         l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
405             '<tr class="list-header">',
406             '<td><span class="list-item"><strong>Date</strong></span></td>',
407             '<td><span class="list-item"><strong>User</strong></span></td>',
408             '<td><span class="list-item"><strong>Action</strong></span></td>',
409             '<td><span class="list-item"><strong>Args</strong></span></td>']
411         for id, date, user, action, args in self.cl.history(self.nodeid):
412             l.append('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'%(
413                date, user, action, args))
414         l.append('</table>')
415         return '\n'.join(l)
417 # XXX new function
418 class Submit(Base):
419     ''' add a submit button for the item
420     '''
421     def __call__(self):
422         if self.nodeid:
423             return '<input type="submit" value="Submit Changes">'
424         elif self.form is not None:
425             return '<input type="submit" value="Submit New Entry">'
426         else:
427             return '[Submit: not called from item]'
431 #   INDEX TEMPLATES
433 class IndexTemplateReplace:
434     def __init__(self, globals, locals, props):
435         self.globals = globals
436         self.locals = locals
437         self.props = props
439     def go(self, text, replace=re.compile(
440             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
441             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
442         return replace.sub(self, text)
443         
444     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
445         if m.group('name'):
446             if m.group('name') in self.props:
447                 text = m.group('text')
448                 replace = IndexTemplateReplace(self.globals, {}, self.props)
449                 return replace.go(m.group('text'))
450             else:
451                 return ''
452         if m.group('display'):
453             command = m.group('command')
454             return eval(command, self.globals, self.locals)
455         print '*** unhandled match', m.groupdict()
457 def sortby(sort_name, columns, filter, sort, group, filterspec):
458     l = []
459     w = l.append
460     for k, v in filterspec.items():
461         k = urllib.quote(k)
462         if type(v) == type([]):
463             w('%s=%s'%(k, ','.join(map(urllib.quote, v))))
464         else:
465             w('%s=%s'%(k, urllib.quote(v)))
466     if columns:
467         w(':columns=%s'%','.join(map(urllib.quote, columns)))
468     if filter:
469         w(':filter=%s'%','.join(map(urllib.quote, filter)))
470     if group:
471         w(':group=%s'%','.join(map(urllib.quote, group)))
472     m = []
473     s_dir = ''
474     for name in sort:
475         dir = name[0]
476         if dir == '-':
477             dir = ''
478         else:
479             name = name[1:]
480         if sort_name == name:
481             if dir == '':
482                 s_dir = '-'
483             elif dir == '-':
484                 s_dir = ''
485         else:
486             m.append(dir+urllib.quote(name))
487     m.insert(0, s_dir+urllib.quote(sort_name))
488     # so things don't get completely out of hand, limit the sort to two columns
489     w(':sort=%s'%','.join(m[:2]))
490     return '&'.join(l)
492 def index(client, templates, db, classname, filterspec={}, filter=[],
493         columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
494         col_re=re.compile(r'<property\s+name="([^>]+)">')):
495     globals = {
496         'plain': Plain(db, templates, classname, form={}),
497         'field': Field(db, templates, classname, form={}),
498         'menu': Menu(db, templates, classname, form={}),
499         'link': Link(db, templates, classname, form={}),
500         'count': Count(db, templates, classname, form={}),
501         'reldate': Reldate(db, templates, classname, form={}),
502         'download': Download(db, templates, classname, form={}),
503         'checklist': Checklist(db, templates, classname, form={}),
504         'list': List(db, templates, classname, form={}),
505         'history': History(db, templates, classname, form={}),
506         'submit': Submit(db, templates, classname, form={}),
507         'note': Note(db, templates, classname, form={})
508     }
509     cl = db.classes[classname]
510     properties = cl.getprops()
511     w = client.write
513     try:
514         template = open(os.path.join(templates, classname+'.filter')).read()
515         all_filters = col_re.findall(template)
516     except IOError, error:
517         if error.errno != errno.ENOENT: raise
518         template = None
519         all_filters = []
520     if template and filter:
521         # display the filter section
522         w('<form>')
523         w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
524         w('<tr class="location-bar">')
525         w(' <th align="left" colspan="2">Filter specification...</th>')
526         w('</tr>')
527         replace = IndexTemplateReplace(globals, locals(), filter)
528         w(replace.go(template))
529         if columns:
530             w('<input type="hidden" name=":columns" value="%s">'%','.join(columns))
531         if filter:
532             w('<input type="hidden" name=":filter" value="%s">'%','.join(filter))
533         if sort:
534             w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
535         if group:
536             w('<input type="hidden" name=":group" value="%s">'%','.join(group))
537         for k, v in filterspec.items():
538             if type(v) == type([]): v = ','.join(v)
539             w('<input type="hidden" name="%s" value="%s">'%(k, v))
540         w('<tr class="location-bar"><td width="1%%">&nbsp;</td>')
541         w('<td><input type="submit" value="Redisplay"></td></tr>')
542         w('</table>')
543         w('</form>')
545     # XXX deviate from spec here ...
546     # load the index section template and figure the default columns from it
547     template = open(os.path.join(templates, classname+'.index')).read()
548     all_columns = col_re.findall(template)
549     if not columns:
550         columns = []
551         for name in all_columns:
552             columns.append(name)
553     else:
554         # re-sort columns to be the same order as all_columns
555         l = []
556         for name in all_columns:
557             if name in columns:
558                 l.append(name)
559         columns = l
561     # now display the index section
562     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
563     w('<tr class="list-header">')
564     for name in columns:
565         cname = name.capitalize()
566         if show_display_form:
567             anchor = "%s?%s"%(classname, sortby(name, columns, filter,
568                 sort, group, filterspec))
569             w('<td><span class="list-item"><a href="%s">%s</a></span></td>'%(
570                 anchor, cname))
571         else:
572             w('<td><span class="list-item">%s</span></td>'%cname)
573     w('</tr>')
575     # this stuff is used for group headings - optimise the group names
576     old_group = None
577     group_names = []
578     if group:
579         for name in group:
580             if name[0] == '-': group_names.append(name[1:])
581             else: group_names.append(name)
583     # now actually loop through all the nodes we get from the filter and
584     # apply the template
585     if nodeids is None:
586         nodeids = cl.filter(filterspec, sort, group)
587     for nodeid in nodeids:
588         # check for a group heading
589         if group_names:
590             this_group = [cl.get(nodeid, name) for name in group_names]
591             if this_group != old_group:
592                 l = []
593                 for name in group_names:
594                     prop = properties[name]
595                     if prop.isLinkType:
596                         group_cl = db.classes[prop.classname]
597                         key = group_cl.getkey()
598                         value = cl.get(nodeid, name)
599                         if value is None:
600                             l.append('[unselected %s]'%prop.classname)
601                         else:
602                             l.append(group_cl.get(cl.get(nodeid, name), key))
603                     elif prop.isMultilinkType:
604                         group_cl = db.classes[prop.classname]
605                         key = group_cl.getkey()
606                         for value in cl.get(nodeid, name):
607                             l.append(group_cl.get(value, key))
608                     else:
609                         value = cl.get(nodeid, name)
610                         if value is None:
611                             value = '[empty %s]'%name
612                         l.append(value)
613                 w('<tr class="section-bar">'
614                   '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
615                     len(columns), ', '.join(l)))
616                 old_group = this_group
618         # display this node's row
619         for value in globals.values():
620             if hasattr(value, 'nodeid'):
621                 value.nodeid = nodeid
622         replace = IndexTemplateReplace(globals, locals(), columns)
623         w(replace.go(template))
625     w('</table>')
627     if not show_display_form:
628         return
630     # now add in the filter/columns/group/etc config table form
631     w('<p><form>')
632     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
633     for k,v in filterspec.items():
634         if type(v) == type([]): v = ','.join(v)
635         w('<input type="hidden" name="%s" value="%s">'%(k, v))
636     if sort:
637         w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
638     names = []
639     for name in cl.getprops().keys():
640         if name in all_filters or name in all_columns:
641             names.append(name)
642     w('<tr class="location-bar">')
643     w('<th align="left" colspan=%s>View customisation...</th></tr>'%
644         (len(names)+1))
645     w('<tr class="location-bar"><th>&nbsp;</th>')
646     for name in names:
647         w('<th>%s</th>'%name.capitalize())
648     w('</tr>')
650     # filter
651     if all_filters:
652         w('<tr><th width="1%" align=right class="location-bar">Filters</th>')
653         for name in names:
654             if name not in all_filters:
655                 w('<td>&nbsp;</td>')
656                 continue
657             if name in filter: checked=' checked'
658             else: checked=''
659             w('<td align=middle>')
660             w('<input type="checkbox" name=":filter" value="%s" %s></td>'%(name,
661                 checked))
662         w('</tr>')
664     # columns
665     if all_columns:
666         w('<tr><th width="1%" align=right class="location-bar">Columns</th>')
667         for name in names:
668             if name not in all_columns:
669                 w('<td>&nbsp;</td>')
670                 continue
671             if name in columns: checked=' checked'
672             else: checked=''
673             w('<td align=middle>')
674             w('<input type="checkbox" name=":columns" value="%s" %s></td>'%(
675                 name, checked))
676         w('</tr>')
678         # group
679         w('<tr><th width="1%" align=right class="location-bar">Grouping</th>')
680         for name in names:
681             prop = properties[name]
682             if name not in all_columns:
683                 w('<td>&nbsp;</td>')
684                 continue
685             if name in group: checked=' checked'
686             else: checked=''
687             w('<td align=middle>')
688             w('<input type="checkbox" name=":group" value="%s" %s></td>'%(
689                 name, checked))
690         w('</tr>')
692     w('<tr class="location-bar"><td width="1%">&nbsp;</td>')
693     w('<td colspan="%s">'%len(names))
694     w('<input type="submit" value="Redisplay"></td></tr>')
695     w('</table>')
696     w('</form>')
700 #   ITEM TEMPLATES
702 class ItemTemplateReplace:
703     def __init__(self, globals, locals, cl, nodeid):
704         self.globals = globals
705         self.locals = locals
706         self.cl = cl
707         self.nodeid = nodeid
709     def go(self, text, replace=re.compile(
710             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
711             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
712         return replace.sub(self, text)
714     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
715         if m.group('name'):
716             if self.nodeid and self.cl.get(self.nodeid, m.group('name')):
717                 replace = ItemTemplateReplace(self.globals, {}, self.cl,
718                     self.nodeid)
719                 return replace.go(m.group('text'))
720             else:
721                 return ''
722         if m.group('display'):
723             command = m.group('command')
724             return eval(command, self.globals, self.locals)
725         print '*** unhandled match', m.groupdict()
727 def item(client, templates, db, classname, nodeid, replace=re.compile(
728             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
729             r'(?P<endprop></property>)|'
730             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
732     globals = {
733         'plain': Plain(db, templates, classname, nodeid),
734         'field': Field(db, templates, classname, nodeid),
735         'menu': Menu(db, templates, classname, nodeid),
736         'link': Link(db, templates, classname, nodeid),
737         'count': Count(db, templates, classname, nodeid),
738         'reldate': Reldate(db, templates, classname, nodeid),
739         'download': Download(db, templates, classname, nodeid),
740         'checklist': Checklist(db, templates, classname, nodeid),
741         'list': List(db, templates, classname, nodeid),
742         'history': History(db, templates, classname, nodeid),
743         'submit': Submit(db, templates, classname, nodeid),
744         'note': Note(db, templates, classname, nodeid)
745     }
747     cl = db.classes[classname]
748     properties = cl.getprops()
750     if properties.has_key('type') and properties.has_key('content'):
751         pass
752         # XXX we really want to return this as a downloadable...
753         #  currently I handle this at a higher level by detecting 'file'
754         #  designators...
756     w = client.write
757     w('<form action="%s%s">'%(classname, nodeid))
758     s = open(os.path.join(templates, classname+'.item')).read()
759     replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
760     w(replace.go(s))
761     w('</form>')
764 def newitem(client, templates, db, classname, form, replace=re.compile(
765             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
766             r'(?P<endprop></property>)|'
767             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
768     globals = {
769         'plain': Plain(db, templates, classname, form=form),
770         'field': Field(db, templates, classname, form=form),
771         'menu': Menu(db, templates, classname, form=form),
772         'link': Link(db, templates, classname, form=form),
773         'count': Count(db, templates, classname, form=form),
774         'reldate': Reldate(db, templates, classname, form=form),
775         'download': Download(db, templates, classname, form=form),
776         'checklist': Checklist(db, templates, classname, form=form),
777         'list': List(db, templates, classname, form=form),
778         'history': History(db, templates, classname, form=form),
779         'submit': Submit(db, templates, classname, form=form),
780         'note': Note(db, templates, classname, form=form)
781     }
783     cl = db.classes[classname]
784     properties = cl.getprops()
786     w = client.write
787     try:
788         s = open(os.path.join(templates, classname+'.newitem')).read()
789     except:
790         s = open(os.path.join(templates, classname+'.item')).read()
791     w('<form action="new%s">'%classname)
792     replace = ItemTemplateReplace(globals, locals(), None, None)
793     w(replace.go(s))
794     w('</form>')
797 # $Log: not supported by cvs2svn $
798 # Revision 1.4  2001/07/28 07:59:53  richard
799 # Replaced errno integers with their module values.
800 # De-tabbed templatebuilder.py
802 # Revision 1.3  2001/07/25 03:39:47  richard
803 # Hrm - displaying links to classes that don't specify a key property. I've
804 # got it defaulting to 'name', then 'title' and then a "random" property (first
805 # one returned by getprops().keys().
806 # Needs to be moved onto the Class I think...
808 # Revision 1.2  2001/07/22 12:09:32  richard
809 # Final commit of Grande Splite
811 # Revision 1.1  2001/07/22 11:58:35  richard
812 # More Grande Splite