X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fhtmltemplate.py;h=1a083b056711fdf9193e0fa013c73145487bd7e1;hb=e6c4c7f8d5631098772610818c9907b51081dac2;hp=de6f10de8c200c229f2ea923cc81fef7c62bd9eb;hpb=d65cfd8585f28fa9411cd5a6f75bd63691b01ea7;p=roundup.git diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py index de6f10d..1a083b0 100644 --- a/roundup/htmltemplate.py +++ b/roundup/htmltemplate.py @@ -15,32 +15,38 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: htmltemplate.py,v 1.23 2001-09-10 09:47:18 richard Exp $ +# $Id: htmltemplate.py,v 1.38 2001-10-31 06:58:51 richard Exp $ import os, re, StringIO, urllib, cgi, errno -import hyperdb, date - -class Base: - def __init__(self, db, templates, classname, nodeid=None, form=None, - filterspec=None): - # TODO: really not happy with the way templates is passed on here - self.db, self.templates = db, templates - self.classname, self.nodeid = classname, nodeid - self.form, self.filterspec = form, filterspec - self.cl = self.db.classes[self.classname] - self.properties = self.cl.getprops() - -class Plain(Base): - ''' display a String property directly; - - display a Date property in a specified time zone with an option to - omit the time from the date stamp; - - for a Link or Multilink property, display the key strings of the - linked nodes (or the ids if the linked class has no key property) - ''' - def __call__(self, property): +import hyperdb, date, password + +# This imports the StructureText functionality for the do_stext function +# get it from http://dev.zope.org/Members/jim/StructuredTextWiki/NGReleases +try: + from StructuredText.StructuredText import HTML as StructuredText +except ImportError: + StructuredText = None + +class TemplateFunctions: + def __init__(self): + self.form = None + self.nodeid = None + self.filterspec = None + self.globals = {} + for key in TemplateFunctions.__dict__.keys(): + if key[:3] == 'do_': + self.globals[key[3:]] = getattr(self, key) + + def do_plain(self, property, escape=0): + ''' display a String property directly; + + display a Date property in a specified time zone with an option to + omit the time from the date stamp; + + for a Link or Multilink property, display the key strings of the + linked nodes (or the ids if the linked class has no key property) + ''' if not self.nodeid and self.form is None: return '[Field: not called from item]' propclass = self.properties[property] @@ -53,6 +59,9 @@ class Plain(Base): if isinstance(propclass, hyperdb.String): if value is None: value = '' else: value = str(value) + elif isinstance(propclass, hyperdb.Password): + if value is None: value = '' + else: value = '*encrypted*' elif isinstance(propclass, hyperdb.Date): value = str(value) elif isinstance(propclass, hyperdb.Interval): @@ -68,13 +77,23 @@ class Plain(Base): value = ', '.join([linkcl.get(i, k) for i in value]) else: s = 'Plain: bad propclass "%s"'%propclass + if escape: + value = cgi.escape(value) return value -class Field(Base): - ''' display a property like the plain displayer, but in a text field - to be edited - ''' - def __call__(self, property, size=None, height=None, showid=0): + def do_stext(self, property, escape=0): + '''Render as structured text using the StructuredText module + (see above for details) + ''' + s = self.do_plain(property, escape=escape) + if not StructuredText: + return s + return StructuredText(s,level=1,header=0) + + def do_field(self, property, size=None, height=None, showid=0): + ''' display a property like the plain displayer, but in a text field + to be edited + ''' if not self.nodeid and self.form is None and self.filterspec is None: return '[Field: not called from item]' propclass = self.properties[property] @@ -104,10 +123,18 @@ class Field(Base): value = cgi.escape(value) value = '"'.join(value.split('"')) s = ''%(property, value, size) + elif isinstance(propclass, hyperdb.Password): + size = size or 30 + s = ''%(property, size) elif isinstance(propclass, hyperdb.Link): linkcl = self.db.classes[propclass.classname] l = [''%property] k = linkcl.labelprop() + s = '' + if value is None: + s = 'selected ' + l.append(''%s) for optionid in linkcl.list(): option = linkcl.get(optionid, k) s = '' @@ -191,13 +221,17 @@ class Menu(Base): return '\n'.join(l) return '[Menu: not a link]' -#XXX deviates from spec -class Link(Base): - ''' for a Link or Multilink property, display the names of the linked - nodes, hyperlinked to the item views on those nodes - for other properties, link to this node with the property as the text - ''' - def __call__(self, property=None, **args): + #XXX deviates from spec + def do_link(self, property=None, is_download=0): + '''For a Link or Multilink property, display the names of the linked + nodes, hyperlinked to the item views on those nodes. + For other properties, link to this node with the property as the + text. + + If is_download is true, append the property value to the generated + URL so that the link may be used as a download link and the + downloaded file name is correct. + ''' if not self.nodeid and self.form is None: return '[Link: not called from item]' propclass = self.properties[property] @@ -205,31 +239,46 @@ class Link(Base): value = self.cl.get(self.nodeid, property) else: if isinstance(propclass, hyperdb.Multilink): value = [] + elif isinstance(propclass, hyperdb.Link): value = None else: value = '' if isinstance(propclass, hyperdb.Link): linkname = propclass.classname - if value is None: - return '[not assigned]' + if value is None: return '[no %s]'%property.capitalize() linkcl = self.db.classes[linkname] k = linkcl.labelprop() linkvalue = linkcl.get(value, k) - return '%s'%(linkname, value, linkvalue) + if is_download: + return '%s'%(linkname, value, + linkvalue, linkvalue) + else: + return '%s'%(linkname, value, linkvalue) if isinstance(propclass, hyperdb.Multilink): linkname = propclass.classname linkcl = self.db.classes[linkname] k = linkcl.labelprop() + if not value : return '[no %s]'%property.capitalize() l = [] for value in value: linkvalue = linkcl.get(value, k) - l.append('%s'%(linkname, value, linkvalue)) + if is_download: + l.append('%s'%(linkname, value, + linkvalue, linkvalue)) + else: + l.append('%s'%(linkname, value, + linkvalue)) return ', '.join(l) - return '%s'%(self.classname, self.nodeid, value) + if isinstance(propclass, hyperdb.String): + if value == '': value = '[no %s]'%property.capitalize() + if is_download: + return '%s'%(self.classname, self.nodeid, + value, value) + else: + return '%s'%(self.classname, self.nodeid, value) -class Count(Base): - ''' for a Multilink property, display a count of the number of links in - the list - ''' - def __call__(self, property, **args): + def do_count(self, property, **args): + ''' for a Multilink property, display a count of the number of links in + the list + ''' if not self.nodeid: return '[Count: not called from item]' propclass = self.properties[property] @@ -238,14 +287,13 @@ class Count(Base): return str(len(value)) return '[Count: not a Multilink]' -# XXX pretty is definitely new ;) -class Reldate(Base): - ''' display a Date property in terms of an interval relative to the - current date (e.g. "+ 3w", "- 2d"). + # XXX pretty is definitely new ;) + def do_reldate(self, property, pretty=0): + ''' display a Date property in terms of an interval relative to the + current date (e.g. "+ 3w", "- 2d"). - with the 'pretty' flag, make it pretty - ''' - def __call__(self, property, pretty=0): + with the 'pretty' flag, make it pretty + ''' if not self.nodeid and self.form is None: return '[Reldate: not called from item]' propclass = self.properties[property] @@ -265,11 +313,10 @@ class Reldate(Base): return pretty return str(interval) -class Download(Base): - ''' show a Link("file") or Multilink("file") property using links that - allow you to download files - ''' - def __call__(self, property, **args): + def do_download(self, property, **args): + ''' show a Link("file") or Multilink("file") property using links that + allow you to download files + ''' if not self.nodeid: return '[Download: not called from item]' propclass = self.properties[property] @@ -288,66 +335,88 @@ class Download(Base): return '[Download: not a link]' -class Checklist(Base): - ''' for a Link or Multilink property, display checkboxes for the available - choices to permit filtering - ''' - def __call__(self, property, **args): + def do_checklist(self, property, **args): + ''' for a Link or Multilink property, display checkboxes for the + available choices to permit filtering + ''' propclass = self.properties[property] + if (not isinstance(propclass, hyperdb.Link) and not + isinstance(propclass, hyperdb.Multilink)): + return '[Checklist: not a link]' + + # get our current checkbox state if self.nodeid: - value = self.cl.get(self.nodeid, property) + # get the info from the node - make sure it's a list + if isinstance(propclass, hyperdb.Link): + value = [self.cl.get(self.nodeid, property)] + else: + value = self.cl.get(self.nodeid, property) elif self.filterspec is not None: + # get the state from the filter specification (always a list) value = self.filterspec.get(property, []) else: + # it's a new node, so there's no state value = [] - if (isinstance(propclass, hyperdb.Link) or - isinstance(propclass, hyperdb.Multilink)): - linkcl = self.db.classes[propclass.classname] - l = [] - k = linkcl.labelprop() - for optionid in linkcl.list(): - option = linkcl.get(optionid, k) - if optionid in value or option in value: - checked = 'checked' - else: - checked = '' - l.append('%s:'%( - option, checked, property, option)) - return '\n'.join(l) - return '[Checklist: not a link]' - -class Note(Base): - ''' display a "note" field, which is a text area for entering a note to - go along with a change. - ''' - def __call__(self, rows=5, cols=80): - # TODO: pull the value from the form - return ''%(rows, - cols) - -# XXX new function -class List(Base): - ''' list the items specified by property using the standard index for - the class - ''' - def __call__(self, property, reverse=0): - propclass = self.properties[property] - if isinstance(not propclass, hyperdb.Multilink): + + # so we can map to the linked node's "lable" property + linkcl = self.db.classes[propclass.classname] + l = [] + k = linkcl.labelprop() + for optionid in linkcl.list(): + option = linkcl.get(optionid, k) + if optionid in value or option in value: + checked = 'checked' + else: + checked = '' + l.append('%s:'%( + option, checked, property, option)) + + # for Links, allow the "unselected" option too + if isinstance(propclass, hyperdb.Link): + if value is None or '-1' in value: + checked = 'checked' + else: + checked = '' + l.append('[unselected]:'%(checked, property)) + return '\n'.join(l) + + def do_note(self, rows=5, cols=80): + ''' display a "note" field, which is a text area for entering a note to + go along with a change. + ''' + # TODO: pull the value from the form + return ''%(rows, cols) + + # XXX new function + def do_list(self, property, reverse=0): + ''' list the items specified by property using the standard index for + the class + ''' + propcl = self.properties[property] + if not isinstance(propcl, hyperdb.Multilink): return '[List: not a Multilink]' - fp = StringIO.StringIO() value = self.cl.get(self.nodeid, property) if reverse: value.reverse() - # TODO: really not happy with the way templates is passed on here - index(fp, self.templates, self.db, propclass.classname, nodeids=value, - show_display_form=0) + + # render the sub-index into a string + fp = StringIO.StringIO() + try: + write_save = self.client.write + self.client.write = fp.write + index = IndexTemplate(self.client, self.templates, propcl.classname) + index.render(nodeids=value, show_display_form=0) + finally: + self.client.write = write_save + return fp.getvalue() -# XXX new function -class History(Base): - ''' list the history of the item - ''' - def __call__(self, **args): + # XXX new function + def do_history(self, **args): + ''' list the history of the item + ''' if self.nodeid is None: return "[History: node doesn't exist]" @@ -364,11 +433,10 @@ class History(Base): l.append('') return '\n'.join(l) -# XXX new function -class Submit(Base): - ''' add a submit button for the item - ''' - def __call__(self): + # XXX new function + def do_submit(self): + ''' add a submit button for the item + ''' if self.nodeid: return '' elif self.form is not None: @@ -386,10 +454,11 @@ class IndexTemplateReplace: self.locals = locals self.props = props - def go(self, text, replace=re.compile( - r'(([^>]+)">(?P.+?))|' - r'(?P[^"]+)">))', re.I|re.S)): - return replace.sub(self, text) + replace=re.compile( + r'(([^>]+)">(?P.+?))|' + r'(?P[^"]+)">))', re.I|re.S) + def go(self, text): + return self.replace.sub(self, text) def __call__(self, m, filter=None, columns=None, sort=None, group=None): if m.group('name'): @@ -404,241 +473,280 @@ class IndexTemplateReplace: return eval(command, self.globals, self.locals) print '*** unhandled match', m.groupdict() -def sortby(sort_name, columns, filter, sort, group, filterspec): - l = [] - w = l.append - for k, v in filterspec.items(): - k = urllib.quote(k) - if type(v) == type([]): - w('%s=%s'%(k, ','.join(map(urllib.quote, v)))) - else: - w('%s=%s'%(k, urllib.quote(v))) - if columns: - w(':columns=%s'%','.join(map(urllib.quote, columns))) - if filter: - w(':filter=%s'%','.join(map(urllib.quote, filter))) - if group: - w(':group=%s'%','.join(map(urllib.quote, group))) - m = [] - s_dir = '' - for name in sort: - dir = name[0] - if dir == '-': - name = name[1:] - else: - dir = '' - if sort_name == name: - if dir == '-': - s_dir = '' - else: - s_dir = '-' - else: - m.append(dir+urllib.quote(name)) - m.insert(0, s_dir+urllib.quote(sort_name)) - # so things don't get completely out of hand, limit the sort to two columns - w(':sort=%s'%','.join(m[:2])) - return '&'.join(l) - -def index(client, templates, db, classname, filterspec={}, filter=[], - columns=[], sort=[], group=[], show_display_form=1, nodeids=None, - col_re=re.compile(r']+)">')): - globals = { - 'plain': Plain(db, templates, classname, filterspec=filterspec), - 'field': Field(db, templates, classname, filterspec=filterspec), - 'menu': Menu(db, templates, classname, filterspec=filterspec), - 'link': Link(db, templates, classname, filterspec=filterspec), - 'count': Count(db, templates, classname, filterspec=filterspec), - 'reldate': Reldate(db, templates, classname, filterspec=filterspec), - 'download': Download(db, templates, classname, filterspec=filterspec), - 'checklist': Checklist(db, templates, classname, filterspec=filterspec), - 'list': List(db, templates, classname, filterspec=filterspec), - 'history': History(db, templates, classname, filterspec=filterspec), - 'submit': Submit(db, templates, classname, filterspec=filterspec), - 'note': Note(db, templates, classname, filterspec=filterspec) - } - cl = db.classes[classname] - properties = cl.getprops() - w = client.write - w('
') - - try: - template = open(os.path.join(templates, classname+'.filter')).read() - all_filters = col_re.findall(template) - except IOError, error: - if error.errno != errno.ENOENT: raise - template = None - all_filters = [] - if template and filter: - # display the filter section - w('') - w('') - w(' ') - w('') - replace = IndexTemplateReplace(globals, locals(), filter) - w(replace.go(template)) - w('') - w('') - w('
Filter specification...
 
') +class IndexTemplate(TemplateFunctions): + def __init__(self, client, templates, classname): + self.client = client + self.templates = templates + self.classname = classname - # If the filters aren't being displayed, then hide their current - # value in the form - if not filter: - for k, v in filterspec.items(): - if type(v) == type([]): v = ','.join(v) - w(''%(k, v)) - - # make sure that the sorting doesn't get lost either - if sort: - w(''%','.join(sort)) - - # XXX deviate from spec here ... - # load the index section template and figure the default columns from it - template = open(os.path.join(templates, classname+'.index')).read() - all_columns = col_re.findall(template) - if not columns: - columns = [] - for name in all_columns: - columns.append(name) - else: - # re-sort columns to be the same order as all_columns - l = [] - for name in all_columns: - if name in columns: - l.append(name) - columns = l - - # now display the index section - w('\n') - w('\n') - for name in columns: - cname = name.capitalize() - if show_display_form: - anchor = "%s?%s"%(classname, sortby(name, columns, filter, - sort, group, filterspec)) - w('\n'%( - anchor, cname)) + # derived + self.db = self.client.db + self.cl = self.db.classes[self.classname] + self.properties = self.cl.getprops() + + TemplateFunctions.__init__(self) + + col_re=re.compile(r']+)">') + def render(self, filterspec={}, filter=[], columns=[], sort=[], group=[], + show_display_form=1, nodeids=None, show_customization=1): + self.filterspec = filterspec + + w = self.client.write + + # get the filter template + try: + filter_template = open(os.path.join(self.templates, + self.classname+'.filter')).read() + all_filters = self.col_re.findall(filter_template) + except IOError, error: + if error.errno not in (errno.ENOENT, errno.ESRCH): raise + filter_template = None + all_filters = [] + + # XXX deviate from spec here ... + # load the index section template and figure the default columns from it + template = open(os.path.join(self.templates, + self.classname+'.index')).read() + all_columns = self.col_re.findall(template) + if not columns: + columns = [] + for name in all_columns: + columns.append(name) else: - w('\n'%cname) - w('\n') - - # this stuff is used for group headings - optimise the group names - old_group = None - group_names = [] - if group: - for name in group: - if name[0] == '-': group_names.append(name[1:]) - else: group_names.append(name) - - # now actually loop through all the nodes we get from the filter and - # apply the template - if nodeids is None: - nodeids = cl.filter(filterspec, sort, group) - for nodeid in nodeids: - # check for a group heading - if group_names: - this_group = [cl.get(nodeid, name) for name in group_names] - if this_group != old_group: - l = [] - for name in group_names: - prop = properties[name] - if isinstance(prop, hyperdb.Link): - group_cl = db.classes[prop.classname] - key = group_cl.getkey() - value = cl.get(nodeid, name) - if value is None: - l.append('[unselected %s]'%prop.classname) - else: - l.append(group_cl.get(cl.get(nodeid, name), key)) - elif isinstance(prop, hyperdb.Multilink): - group_cl = db.classes[prop.classname] - key = group_cl.getkey() - for value in cl.get(nodeid, name): - l.append(group_cl.get(value, key)) - else: - value = cl.get(nodeid, name) - if value is None: - value = '[empty %s]'%name - l.append(value) - w('' - ''%( - len(columns), ', '.join(l))) - old_group = this_group - - # display this node's row - for value in globals.values(): - if hasattr(value, 'nodeid'): - value.nodeid = nodeid - replace = IndexTemplateReplace(globals, locals(), columns) - w(replace.go(template)) - - w('
%s%s
%s
') - - if not show_display_form: - return - - # now add in the filter/columns/group/etc config table form - w('

') - w('\n') - names = [] - for name in cl.getprops().keys(): - if name in all_filters or name in all_columns: - names.append(name) - w('') - w('\n'% - (len(names)+1)) - w('') - for name in names: - w(''%name.capitalize()) - w('\n') - - # filter - if all_filters: - w('\n') - for name in names: - if name not in all_filters: - w('') - continue - if name in filter: checked=' checked' - else: checked='' - w('\n'%( - name, checked)) - w('\n') + # re-sort columns to be the same order as all_columns + l = [] + for name in all_columns: + if name in columns: + l.append(name) + columns = l - # columns - if all_columns: - w('\n') - for name in names: - if name not in all_columns: - w('') - continue - if name in columns: checked=' checked' - else: checked='' - w('\n'%( - name, checked)) + # display the filter section + if (hasattr(self.client, 'FILTER_POSITION') and + self.client.FILTER_POSITION in ('top and bottom', 'top')): + w('\n') + self.filter_section(filter_template, filter, columns, group, + all_filters, all_columns, show_display_form, show_customization) + w('\n') + + # make sure that the sorting doesn't get lost either + if sort: + w(''%','.join(sort)) + + # now display the index section + w('
View customisation...
 %s
Filters \n') - w('
Columns \n') - w('
\n') + w('\n') + for name in columns: + cname = name.capitalize() + if show_display_form: + sb = self.sortby(name, filterspec, columns, filter, group, sort) + anchor = "%s?%s"%(self.classname, sb) + w('\n'%( + anchor, cname)) + else: + w('\n'%cname) w('\n') - # group - w('\n') - for name in names: - prop = properties[name] - if name not in all_columns: - w('') - continue - if name in group: checked=' checked' - else: checked='' - w('\n'%( - name, checked)) - w('\n') + # this stuff is used for group headings - optimise the group names + old_group = None + group_names = [] + if group: + for name in group: + if name[0] == '-': group_names.append(name[1:]) + else: group_names.append(name) + + # now actually loop through all the nodes we get from the filter and + # apply the template + if nodeids is None: + nodeids = self.cl.filter(filterspec, sort, group) + for nodeid in nodeids: + # check for a group heading + if group_names: + this_group = [self.cl.get(nodeid, name) for name in group_names] + if this_group != old_group: + l = [] + for name in group_names: + prop = self.properties[name] + if isinstance(prop, hyperdb.Link): + group_cl = self.db.classes[prop.classname] + key = group_cl.getkey() + value = self.cl.get(nodeid, name) + if value is None: + l.append('[unselected %s]'%prop.classname) + else: + l.append(group_cl.get(self.cl.get(nodeid, + name), key)) + elif isinstance(prop, hyperdb.Multilink): + group_cl = self.db.classes[prop.classname] + key = group_cl.getkey() + for value in self.cl.get(nodeid, name): + l.append(group_cl.get(value, key)) + else: + value = self.cl.get(nodeid, name) + if value is None: + value = '[empty %s]'%name + else: + value = str(value) + l.append(value) + w('' + ''%( + len(columns), ', '.join(l))) + old_group = this_group + + # display this node's row + replace = IndexTemplateReplace(self.globals, locals(), columns) + self.nodeid = nodeid + w(replace.go(template)) + self.nodeid = None + + w('
%s%s
Grouping \n') - w('
%s
') - w(' ') - w(''%len(names)) - w('\n') - w('\n') - w('\n') + # display the filter section + if (hasattr(self.client, 'FILTER_POSITION') and + self.client.FILTER_POSITION in ('top and bottom', 'bottom')): + w('

\n') + self.filter_section(filter_template, filter, columns, group, + all_filters, all_columns, show_display_form, show_customization) + w('
\n') + + + def filter_section(self, template, filter, columns, group, all_filters, + all_columns, show_display_form, show_customization): + + w = self.client.write + + if template and filter: + # display the filter section + w('') + w('') + w(' ') + w('') + replace = IndexTemplateReplace(self.globals, locals(), filter) + w(replace.go(template)) + w('') + w('') + w('
Filter specification...
 
') + + # now add in the filter/columns/group/etc config table form + w('' % + show_customization ) + w('\n') + names = [] + for name in self.properties.keys(): + if name in all_filters or name in all_columns: + names.append(name) + w('') + if show_customization: + action = '-' + else: + action = '+' + # hide the values for filters, columns and grouping in the form + # if the customization widget is not visible + for name in names: + if all_filters and name in filter: + w('' % name) + if all_columns and name in columns: + w('' % name) + if all_columns and name in group: + w('' % name) + if show_display_form: + # TODO: The widget style can go into the stylesheet + w('\n'%(len(names)+1, action)) + if show_customization: + w('') + for name in names: + w(''%name.capitalize()) + w('\n') + + # Filter + if all_filters: + w('\n') + for name in names: + if name not in all_filters: + w('') + continue + if name in filter: checked=' checked' + else: checked='' + w('\n'%(name, checked)) + w('\n') + + # Columns + if all_columns: + w('\n') + for name in names: + if name not in all_columns: + w('') + continue + if name in columns: checked=' checked' + else: checked='' + w('\n'%(name, checked)) + w('\n') + + # Grouping + w('\n') + for name in names: + prop = self.properties[name] + if name not in all_columns: + w('') + continue + if name in group: checked=' checked' + else: checked='' + w('\n'%(name, checked)) + w('\n') + + w('') + w('') + w('\n') + + w('
' + ' View ' + 'customisation...
 %s
' + 'Filters \n') + w('
' + 'Columns \n') + w('
' + 'Grouping \n') + w('
 '%len(names)) + w('
\n') + + def sortby(self, sort_name, filterspec, columns, filter, group, sort): + l = [] + w = l.append + for k, v in filterspec.items(): + k = urllib.quote(k) + if type(v) == type([]): + w('%s=%s'%(k, ','.join(map(urllib.quote, v)))) + else: + w('%s=%s'%(k, urllib.quote(v))) + if columns: + w(':columns=%s'%','.join(map(urllib.quote, columns))) + if filter: + w(':filter=%s'%','.join(map(urllib.quote, filter))) + if group: + w(':group=%s'%','.join(map(urllib.quote, group))) + m = [] + s_dir = '' + for name in sort: + dir = name[0] + if dir == '-': + name = name[1:] + else: + dir = '' + if sort_name == name: + if dir == '-': + s_dir = '' + else: + s_dir = '-' + else: + m.append(dir+urllib.quote(name)) + m.insert(0, s_dir+urllib.quote(sort_name)) + # so things don't get completely out of hand, limit the sort to + # two columns + w(':sort=%s'%','.join(m[:2])) + return '&'.join(l) # # ITEM TEMPLATES @@ -650,10 +758,11 @@ class ItemTemplateReplace: self.cl = cl self.nodeid = nodeid - def go(self, text, replace=re.compile( - r'(([^>]+)">(?P.+?))|' - r'(?P[^"]+)">))', re.I|re.S)): - return replace.sub(self, text) + replace=re.compile( + r'(([^>]+)">(?P.+?))|' + r'(?P[^"]+)">))', re.I|re.S) + def go(self, text): + return self.replace.sub(self, text) def __call__(self, m, filter=None, columns=None, sort=None, group=None): if m.group('name'): @@ -668,83 +777,133 @@ class ItemTemplateReplace: return eval(command, self.globals, self.locals) print '*** unhandled match', m.groupdict() -def item(client, templates, db, classname, nodeid, replace=re.compile( - r'((?P[^>]+)">)|' - r'(?P)|' - r'(?P[^"]+)">))', re.I)): - - globals = { - 'plain': Plain(db, templates, classname, nodeid), - 'field': Field(db, templates, classname, nodeid), - 'menu': Menu(db, templates, classname, nodeid), - 'link': Link(db, templates, classname, nodeid), - 'count': Count(db, templates, classname, nodeid), - 'reldate': Reldate(db, templates, classname, nodeid), - 'download': Download(db, templates, classname, nodeid), - 'checklist': Checklist(db, templates, classname, nodeid), - 'list': List(db, templates, classname, nodeid), - 'history': History(db, templates, classname, nodeid), - 'submit': Submit(db, templates, classname, nodeid), - 'note': Note(db, templates, classname, nodeid) - } - - cl = db.classes[classname] - properties = cl.getprops() - - if properties.has_key('type') and properties.has_key('content'): - pass - # XXX we really want to return this as a downloadable... - # currently I handle this at a higher level by detecting 'file' - # designators... - - w = client.write - w('
'%(classname, nodeid)) - s = open(os.path.join(templates, classname+'.item')).read() - replace = ItemTemplateReplace(globals, locals(), cl, nodeid) - w(replace.go(s)) - w('
') - - -def newitem(client, templates, db, classname, form, replace=re.compile( - r'((?P[^>]+)">)|' - r'(?P)|' - r'(?P[^"]+)">))', re.I)): - globals = { - 'plain': Plain(db, templates, classname, form=form), - 'field': Field(db, templates, classname, form=form), - 'menu': Menu(db, templates, classname, form=form), - 'link': Link(db, templates, classname, form=form), - 'count': Count(db, templates, classname, form=form), - 'reldate': Reldate(db, templates, classname, form=form), - 'download': Download(db, templates, classname, form=form), - 'checklist': Checklist(db, templates, classname, form=form), - 'list': List(db, templates, classname, form=form), - 'history': History(db, templates, classname, form=form), - 'submit': Submit(db, templates, classname, form=form), - 'note': Note(db, templates, classname, form=form) - } - - cl = db.classes[classname] - properties = cl.getprops() - - w = client.write - try: - s = open(os.path.join(templates, classname+'.newitem')).read() - except: - s = open(os.path.join(templates, classname+'.item')).read() - w('
'%classname) - for key in form.keys(): - if key[0] == ':': - value = form[key].value - if type(value) != type([]): value = [value] - for value in value: - w(''%(key, value)) - replace = ItemTemplateReplace(globals, locals(), None, None) - w(replace.go(s)) - w('
') + +class ItemTemplate(TemplateFunctions): + def __init__(self, client, templates, classname): + self.client = client + self.templates = templates + self.classname = classname + + # derived + self.db = self.client.db + self.cl = self.db.classes[self.classname] + self.properties = self.cl.getprops() + + TemplateFunctions.__init__(self) + + def render(self, nodeid): + self.nodeid = nodeid + + if (self.properties.has_key('type') and + self.properties.has_key('content')): + pass + # XXX we really want to return this as a downloadable... + # currently I handle this at a higher level by detecting 'file' + # designators... + + w = self.client.write + w('
'%(self.classname, nodeid)) + s = open(os.path.join(self.templates, self.classname+'.item')).read() + replace = ItemTemplateReplace(self.globals, locals(), self.cl, nodeid) + w(replace.go(s)) + w('
') + + +class NewItemTemplate(TemplateFunctions): + def __init__(self, client, templates, classname): + self.client = client + self.templates = templates + self.classname = classname + + # derived + self.db = self.client.db + self.cl = self.db.classes[self.classname] + self.properties = self.cl.getprops() + + TemplateFunctions.__init__(self) + + def render(self, form): + self.form = form + w = self.client.write + c = self.classname + try: + s = open(os.path.join(self.templates, c+'.newitem')).read() + except: + s = open(os.path.join(self.templates, c+'.item')).read() + w('
'%c) + for key in form.keys(): + if key[0] == ':': + value = form[key].value + if type(value) != type([]): value = [value] + for value in value: + w(''%(key, value)) + replace = ItemTemplateReplace(self.globals, locals(), None, None) + w(replace.go(s)) + w('
') # # $Log: not supported by cvs2svn $ +# Revision 1.37 2001/10/31 06:24:35 richard +# Added do_stext to htmltemplate, thanks Brad Clements. +# +# Revision 1.36 2001/10/28 22:51:38 richard +# Fixed ENOENT/WindowsError thing, thanks Juergen Hermann +# +# Revision 1.35 2001/10/24 00:04:41 richard +# Removed the "infinite authentication loop", thanks Roch'e +# +# Revision 1.34 2001/10/23 22:56:36 richard +# Bugfix in filter "widget" placement, thanks Roch'e +# +# Revision 1.33 2001/10/23 01:00:18 richard +# Re-enabled login and registration access after lopping them off via +# disabling access for anonymous users. +# Major re-org of the htmltemplate code, cleaning it up significantly. Fixed +# a couple of bugs while I was there. Probably introduced a couple, but +# things seem to work OK at the moment. +# +# Revision 1.32 2001/10/22 03:25:01 richard +# Added configuration for: +# . anonymous user access and registration (deny/allow) +# . filter "widget" location on index page (top, bottom, both) +# Updated some documentation. +# +# Revision 1.31 2001/10/21 07:26:35 richard +# feature #473127: Filenames. I modified the file.index and htmltemplate +# source so that the filename is used in the link and the creation +# information is displayed. +# +# Revision 1.30 2001/10/21 04:44:50 richard +# bug #473124: UI inconsistency with Link fields. +# This also prompted me to fix a fairly long-standing usability issue - +# that of being able to turn off certain filters. +# +# Revision 1.29 2001/10/21 00:17:56 richard +# CGI interface view customisation section may now be hidden (patch from +# Roch'e Compaan.) +# +# Revision 1.28 2001/10/21 00:00:16 richard +# Fixed Checklist function - wasn't always working on a list. +# +# Revision 1.27 2001/10/20 12:13:44 richard +# Fixed grouping of non-str properties (thanks Roch'e Compaan) +# +# Revision 1.26 2001/10/14 10:55:00 richard +# Handle empty strings in HTML template Link function +# +# Revision 1.25 2001/10/09 07:25:59 richard +# Added the Password property type. See "pydoc roundup.password" for +# implementation details. Have updated some of the documentation too. +# +# Revision 1.24 2001/09/27 06:45:58 richard +# *gak* ... xmp is Old Skool apparently. Am using pre again by have the option +# on the plain() template function to escape the text for HTML. +# +# Revision 1.23 2001/09/10 09:47:18 richard +# Fixed bug in the generation of links to Link/Multilink in indexes. +# (thanks Hubert Hoegl) +# Added AssignedTo to the "classic" schema's item page. +# # Revision 1.22 2001/08/30 06:01:17 richard # Fixed missing import in mailgw :( #