def expand_macro(self, formatter, name, text): req = formatter.req context = formatter.context resource = context.resource # Process the arguments. args, kwargs = parse_args(text) if 'ticket' not in kwargs and len(args)>0: kwargs['ticket'] = args[0] elif 'ticket' not in kwargs and not len(args): kwargs['ticket'] = str( WikiPage(self.env, resource).name ) # This seems to provide the correct ticket id. try: kwargs['ticket'] = int( kwargs.get('ticket').lstrip('#') ) except ValueError: raise TracError('No ticket id supplied or it is not valid integer value.') ticket = Ticket( self.env, kwargs['ticket'] ) self.childtickets = {} # { parent -> children } - 1:n db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT ticket,value FROM ticket_custom WHERE name='parent'") for child,parent in cursor.fetchall(): if parent and re.match('#\d+',parent): self.childtickets.setdefault( int(parent.lstrip('#')), [] ).append(child) # First ticket has no indentation. ticket['indent'] = '0' # List of tickets that will be displayed. if as_bool( kwargs.get('root') ): self.tickets = [ ticket, ] else: self.tickets = [] # Do this neater! self.indent_children(ticket) def ticket_anchor(t): return tag.a( '#%s' % t.id, class_=t['status'], href=req.href.ticket(int(t.id)), title="%s : %s : %s" % (t['type'],t['owner'],t['status'])) def_list = tag.dl( [( tag.dt(ticket_anchor(t),style='padding-left: %spx;' % (t['indent']*20)), tag.dd("%s" % t['summary'])) for t in self.tickets], class_='wiki compact', ) if as_bool( kwargs.get('border') ): return tag.fieldset( def_list, tag.legend('Ticket Child Tree (#%s)' % ticket.id), class_='description', ) else: return tag.div(def_list)
def _field_group(self, req, group): fieldset_id = 'properties_%s' % group['name'] table_id = 'table_%s' % group['name'] fieldset = tag.fieldset(id=fieldset_id, class_='property_group') fieldset.append(tag.legend(group['label'])) fieldset.append(tag.table(id=table_id)) return fieldset
def _generate_attachmentflags_fieldset(self, readonly=True, current_flags=None, form=False): fields = Fragment() for flag in self.known_flags: flagid = 'flag_' + flag if current_flags and flag in current_flags: date = datetime.datetime.fromtimestamp( current_flags[flag]["updated_on"], utc) text = tag.span( tag.strong(flag), " set by ", tag.em(current_flags[flag]["updated_by"]), ", ", tag.span(pretty_timedelta(date), title=format_datetime(date)), " ago") if readonly == True: fields += tag.input(text, \ type='checkbox', id=flagid, \ name=flagid, checked="checked", disabled="true") + tag.br() else: fields += tag.input(text, \ type='checkbox', id=flagid, \ name=flagid, checked="checked") + tag.br() else: if readonly == True: fields += tag.input(flag, \ type='checkbox', id=flagid, \ name=flagid, disabled="true") + tag.br() else: fields += tag.input(flag, \ type='checkbox', id=flagid, \ name=flagid) + tag.br() if form and not readonly: return tag.form(tag.fieldset( tag.legend("Attachment Flags") + fields, tag.input(type="hidden", name="action", value="update_flags"), tag.input(type="submit", value="Update flags")), method="POST") return tag.fieldset(tag.legend("Attachment Flags") + fields)
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/register') and ( req.method == 'GET' or 'registration_error' in data): question = None if self.question is not None and len(self.question) > 0: question = tag.label( tag(self.question + " "), tag.input(id='question', type='text', name='question')) # First Fieldset of the registration form XPath match xpath_match = '//form[@id="acctmgr_registerform"]/fieldset[1]' if question is None: return stream else: return stream | Transformer(xpath_match). \ append(tag(Markup(question))) # Admin Configuration elif req.path_info.startswith('/admin/accounts/config'): api_html = tag.div( tag.label("Question:", for_="registerquestion_question") + tag.input(class_="textwidget", name="question", value=self.question, size=60) ) + tag.div( tag.label("Answer:", for_="registerquestion_answer") + tag.input(class_="textwidget", name="answer", value=self.answer, size=60) ) + tag.div( tag.label("Hint:", for_="registerquestion_hint") + tag.input( class_="textwidget", name="hint", value=self.hint, size=60) ) + tag.br() # First fieldset of the Account Manager config form xpath_match = '//form[@id="accountsconfig"]/fieldset[1]' return stream | Transformer(xpath_match). \ before(tag.fieldset(tag.legend("Anti-Robot Question For Registration") + api_html)) return stream
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/register') and ( req.method == 'GET' or 'registration_error' in data): question = None if self.question is not None and len(self.question)>0: question = tag.label( tag(self.question + " "), tag.input(id='question', type='text', name='question') ) # First Fieldset of the registration form XPath match xpath_match = '//form[@id="acctmgr_registerform"]/fieldset[1]' if question is None: return stream else: return stream | Transformer(xpath_match). \ append(tag(Markup(question))) # Admin Configuration elif req.path_info.startswith('/admin/accounts/config'): api_html = tag.div( tag.label("Question:", for_="registerquestion_question") + tag.input(class_="textwidget", name="question", value=self.question, size=60) ) + tag.div( tag.label("Answer:", for_="registerquestion_answer") + tag.input(class_="textwidget", name="answer", value=self.answer, size=60) ) + tag.div( tag.label("Hint:", for_="registerquestion_hint") + tag.input(class_="textwidget", name="hint", value=self.hint, size=60) ) + tag.br() # First fieldset of the Account Manager config form xpath_match = '//form[@id="accountsconfig"]/fieldset[1]' return stream | Transformer(xpath_match). \ before(tag.fieldset(tag.legend("Anti-Robot Question For Registration") + api_html)) return stream
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/register') and ( req.method == 'GET' or 'registration_error' in data or 'captcha_error' in req.session): if not (self.private_key or self.private_key): return stream captcha_opts = tag.script("""\ var RecaptchaOptions = { theme: "%s", lang: "%s" }""" % (self.theme, self.lang), type='text/javascript') captcha_js = captcha.displayhtml( self.public_key, use_ssl=True, error='reCAPTCHA incorrect. Please try again.' ) # First Fieldset of the registration form XPath match xpath_match = '//form[@id="acctmgr_registerform"]/fieldset[1]' return stream | Transformer(xpath_match). \ append(captcha_opts + tag(Markup(captcha_js))) # Admin Configuration elif req.path_info.startswith('/admin/accounts/config'): api_html = tag.div( tag.label("Public Key:", for_="recaptcha_public_key") + tag.input(class_="textwidget", name="recaptcha_public_key", value=self.public_key, size=40) ) + tag.div( tag.label("Private Key:", for_="recaptcha_private_key") + tag.input(class_="textwidget", name="recaptcha_private_key", value=self.private_key, size=40) ) if not (self.private_key or self.public_key): api_html = tag.div( tag.a("Generate a reCAPTCHA API key for this Trac " "instance domain.", target="_blank", href="https://recaptcha.net/api/getkey?domain=%s&" "app=TracRecaptchaRegister" % req.environ.get('SERVER_NAME') ) ) + tag.br() + api_html theme_html = tag.div( tag.label("reCPATCHA theme:", for_='recaptcha_theme') + tag.select( tag.option("Black Glass", value="blackglass", selected=self.theme=='blackglass' or None) + tag.option("Clean", value="clean", selected=self.theme=='clean' or None) + tag.option("Red", value="red", selected=self.theme=='red' or None) + tag.option("White", value="white", selected=self.theme=='white' or None), name='recaptcha_theme' ) ) language_html = tag.div( tag.label("reCAPTCHA language:", for_='recaptcha_lang') + tag.select( tag.option("Dutch", value="nl", selected=self.lang=='nl' or None) + tag.option("English", value="en", selected=self.lang=='en' or None) + tag.option("French", selected=self.lang=='fr' or None) + tag.option("German", value="de", selected=self.lang=='de' or None) + tag.option("Portuguese", value="pt", selected=self.lang=='pt' or None) + tag.option("Russian", value="ru", selected=self.lang=='ru' or None) + tag.option("Spanish", value="es", selected=self.lang=='es' or None) + tag.option("Turkish", value="tr", selected=self.lang=='tr' or None), name='recaptcha_lang')) # First fieldset of the Account Manager config form xpath_match = '//form[@id="accountsconfig"]/fieldset[1]' return stream | Transformer(xpath_match). \ before(tag.fieldset(tag.legend("reCAPTCHA") + api_html + tag.br() + theme_html + language_html)) return stream
def filter_stream(self, req, method, filename, stream, data): if req.path_info.startswith('/register') and ( req.method == 'GET' or 'registration_error' in data or 'captcha_error' in req.session): if not (self.private_key or self.private_key): return stream captcha_opts = tag.script("""\ var RecaptchaOptions = { theme: "%s", lang: "%s" }""" % (self.theme, self.lang), type='text/javascript') captcha_js = captcha.displayhtml( self.public_key, use_ssl=req.scheme == 'https', error='reCAPTCHA incorrect. Please try again.') # First Fieldset of the registration form XPath match xpath_match = '//form[@id="acctmgr_registerform"]/fieldset[1]' return stream | Transformer(xpath_match). \ append(captcha_opts + tag(Markup(captcha_js))) # Admin Configuration elif req.path_info.startswith('/admin/accounts/config'): api_html = tag.div( tag.label("Public Key:", for_="recaptcha_public_key") + tag.input(class_="textwidget", name="recaptcha_public_key", value=self.public_key, size=40) ) + tag.div( tag.label("Private Key:", for_="recaptcha_private_key") + tag.input(class_="textwidget", name="recaptcha_private_key", value=self.private_key, size=40)) if not (self.private_key or self.public_key): api_html = tag.div( tag.a( "Generate a reCAPTCHA API key for this Trac " "instance domain.", target="_blank", href="http://recaptcha.net/api/getkey?domain=%s&" "app=TracRecaptchaRegister" % req.environ.get('SERVER_NAME'))) + tag.br() + api_html theme_html = tag.div( tag.label("reCPATCHA theme:", for_='recaptcha_theme') + tag.select( tag.option("Black Glass", value="blackglass", selected=self.theme == 'blackglass' or None) + tag.option("Clean", value="clean", selected=self.theme == 'clean' or None) + tag.option("Red", value="red", selected=self.theme == 'red' or None) + tag.option("White", value="white", selected=self.theme == 'white' or None), name='recaptcha_theme')) language_html = tag.div( tag.label("reCAPTCHA language:", for_='recaptcha_lang') + tag.select(tag.option( "Dutch", value="nl", selected=self.lang == 'nl' or None ) + tag.option( "English", value="en", selected=self.lang == 'en' or None ) + tag.option("French", selected=self.lang == 'fr' or None) + tag.option("German", value="de", selected=self.lang == 'de' or None) + tag.option("Portuguese", value="pt", selected=self.lang == 'pt' or None) + tag.option("Russian", value="ru", selected=self.lang == 'ru' or None) + tag.option("Spanish", value="es", selected=self.lang == 'es' or None) + tag.option("Turkish", value="tr", selected=self.lang == 'tr' or None), name='recaptcha_lang')) # First fieldset of the Account Manager config form xpath_match = '//form[@id="accountsconfig"]/fieldset[1]' return stream | Transformer(xpath_match). \ before(tag.fieldset(tag.legend("reCAPTCHA") + api_html + tag.br() + theme_html + language_html)) return stream
def expand_macro(self, formatter, name, args): req = formatter.req tag_system = TagSystem(self.env) all_releases = natural_sort([r.id for r, _ in tag_system.query(req, 'realm:wiki release')]) all_categories = sorted([r.id for r, _ in tag_system.query(req, 'realm:wiki type')]) hide_release_picker = False hide_fieldset_legend = False hide_fieldset_description = False other = [] if args: categories = [] releases = [] for arg in args.split(): if arg in all_releases: hide_release_picker = True releases.append(arg) elif arg in all_categories: categories.append(arg) else: other.append(arg) if len(categories) or len(releases): hide_fieldset_description = True if not len(categories): categories = all_categories elif len(categories) == 1: hide_fieldset_legend = True if not len(releases): releases = all_releases else: categories = all_categories releases = all_releases if 'update_th_filter' in req.args: show_releases = req.args.get('release', ['0.11']) if isinstance(show_releases, basestring): show_releases = [show_releases] req.session['th_release_filter'] = ','.join(show_releases) else: show_releases = req.session.get('th_release_filter', '0.11').split(',') output = "" if not hide_release_picker: style = "text-align:right; padding-top:1em; margin-right:5em;" form = builder.form('\n', style=style, method="get") style = "font-size:xx-small;" span = builder.span("Show hacks for releases:", style=style) for version in releases: inp = builder.input(version, type_="checkbox", name="release", value=version) if version in show_releases: inp(checked="checked") span(inp, '\n') style = "font-size:xx-small; padding:0; border:solid 1px black;" span(builder.input(name="update_th_filter", type_="submit", style=style, value="Update"), '\n') form('\n', span, '\n') output = "%s%s\n" % (output, form) def link(resource): return render_resource_link(self.env, formatter.context, resource, 'compact') for category in categories: page = WikiPage(self.env, category) match = self.title_extract.search(page.text) if match: cat_title = '%s' % match.group(1).strip() cat_body = self.title_extract.sub('', page.text, 1) else: cat_title = '%s' % category cat_body = page.text cat_body = self.self_extract.sub('', cat_body).strip() style = "padding:1em; margin:0em 5em 2em 5em; border:1px solid #999;" fieldset = builder.fieldset('\n', style=style) if not hide_fieldset_legend: legend = builder.legend(style="color: #999;") legend(builder.a(cat_title, href=self.env.href.wiki(category))) fieldset(legend, '\n') if not hide_fieldset_description: fieldset(builder.p(wiki_to_html(cat_body, self.env, req))) ul = builder.ul('\n', class_="listtagged") query = 'realm:wiki (%s) %s %s' % \ (' or '.join(show_releases), category, ' '.join(other)) lines = 0 for resource, tags in tag_system.query(req, query): # filter out the page used to make important tags # persistent if resource.id == "tags/persistent": continue lines += 1 li = builder.li(link(resource), ': ') page = WikiPage(self.env, resource) match = self.title_extract.search(page.text) description = "''no description available''" if match: if match.group(1): description = match.group(1).strip() li(wiki_to_oneliner(description, self.env, req=req)) if tags: if hide_fieldset_legend == False and category in tags: tags.remove(category) self.log.debug("hide %s: no legend" % category) for o in other: if o in tags: tags.remove(o) rendered_tags = [ link(resource('tag', tag)) for tag in natural_sort(tags) ] span = builder.span(style="font-size:xx-small;") span(' (tags: ', rendered_tags[0], [(', ', tag) for tag in rendered_tags[1:]], ')') li(span) ul(li, '\n') if lines: fieldset(ul, '\n') else: message = "No results for %s." % \ (hide_release_picker and "this version" or "your selection") fieldset(builder.p(builder.em(message)), '\n') output = "%s%s\n" % (output, fieldset) return output
def AddComment(macro, environ, data, *args, **kwargs): """Display an add comment form allowing users to post comments. This macro allows you to display an add comment form on the current page allowing users to post comments. The comments are added to the page's content itself. **Arguments:** //No Arguments// **Example(s):** {{{ <<AddComment>> }}} <<AddComment>> """ # Setup info and defaults parser = environ.parser request = environ.request page = data["page"] page_name = page["name"] page_text = page["text"] # Get the data from the POST comment = request.kwargs.get("comment", "") action = request.kwargs.get("action", "") author = request.kwargs.get("author", environ._user()) # Ensure <<AddComment>> is not present in comment, so that infinite # recursion does not occur. comment = re.sub("(^|[^!])(\<\<AddComment)", "\\1!\\2", comment) the_preview = None the_comment = None # If we are submitting or previewing, inject comment as it should look if action == "preview": the_preview = tag.div(tag.h1("Preview"), id="preview") the_preview += tag.div(parser.generate(comment, environ=(environ, data)), class_="article") # When submitting, inject comment before macro if comment and action == "save": new_text = "" comment_text = "\n==== Comment by %s on %s ====\n\n%s\n\n" % ( author, time.strftime('%c', time.localtime()), comment) for line in page_text.split("\n"): if line.find("<<AddComment") == 0: new_text += comment_text new_text += line + "\n" search = environ.search storage = environ.storage storage.reopen() search.update(environ) storage.save_text(page_name, new_text, author, "Comment added by %s" % author) search.update_page(environ.get_page(page_name), page_name, text=new_text) the_comment = tag.div(parser.generate(comment_text, environ=(environ, data)), class_="article") the_form = tag.form( tag.input(type="hidden", name="parent", value=page["node"]), tag.fieldset( tag.legend("Add Comment"), tag.p(tag.textarea( (not action in ("cancel", "save") and comment or ""), id="comment", name="comment", cols=80, rows=5), class_="text"), tag.h4(tag.label("Your email or username:"******"author")), tag.p(tag.input(id="author", name="author", type="text", value=(not action in ("cancel", "save")) and author or ""), class_="input"), tag.p(tag.button("Preview", type="submit", name="action", value="preview"), tag.button("Save", type="submit", name="action", value="save"), tag.button("Cancel", type="submit", name="action", value="cancel"), class_="button"), ), method="post", action="") return tag(the_preview, the_comment, the_form)
class AddCommentMacro(WikiMacroBase): """A macro to add comments to a page. Usage: {{{ [[AddComment]] }}} The macro accepts one optional argument that allows appending to the wiki page even though user may not have modify permission: {{{ [[AddComment(appendonly)]] }}} """ implements(IWikiMacroProvider, IRequestFilter, IMacroPoster) def expand_macro(self, formatter, name, content): args, kw = parse_args(content) req = formatter.req context = formatter.context # Prevent multiple inclusions - store a temp in req if hasattr(req, 'addcommentmacro'): raise TracError('\'AddComment\' macro cannot be included twice.') req.addcommentmacro = True # Prevent it being used outside of wiki page context resource = context.resource if not resource.realm == 'wiki': raise TracError( '\'AddComment\' macro can only be used in Wiki pages.') # Setup info and defaults authname = req.authname page = WikiPage(self.env, resource) page_url = req.href.wiki(resource.id) wikipreview = req.args.get("preview", "") # Can this user add a comment to this page? appendonly = ('appendonly' in args) cancomment = False if page.readonly: if 'WIKI_ADMIN' in req.perm(resource): cancomment = True elif 'WIKI_MODIFY' in req.perm(resource): cancomment = True elif appendonly and 'WIKI_VIEW' in req.perm(resource): cancomment = True else: self.log.debug( 'Insufficient privileges for %s to AddComment to %s', req.authname, resource.id) # Get the data from the POST comment = req.args.get("addcomment", "") preview = req.args.get("previewaddcomment", "") cancel = req.args.get("canceladdcomment", "") submit = req.args.get("submitaddcomment", "") if not cancel and req.authname == 'anonymous': authname = req.args.get("authoraddcomment", authname) # Ensure [[AddComment]] is not present in comment, so that infinite # recursion does not occur. comment = to_unicode( re.sub('(^|[^!])(\[\[AddComment)', '\\1!\\2', comment)) the_preview = the_message = the_form = tag() # If we are submitting or previewing, inject comment as it should look if cancomment and comment and (preview or submit): heading = tag.h4("Comment by ", authname, " on ", to_unicode(time.strftime('%c', time.localtime())), id="commentpreview") if preview: the_preview = tag.div(heading, format_to_html(self.env, context, comment), class_="wikipage", id="preview") # Check the form_token form_ok = True if submit and req.args.get('__FORM_TOKEN', '') != req.form_token: form_ok = False the_message = tag.div(tag.strong("ERROR: "), "AddComment received incorrect form token. " "Do you have cookies enabled?", class_="system-message") # When submitting, inject comment before macro if comment and submit and cancomment and form_ok: submitted = False newtext = "" for line in page.text.splitlines(): if line.find('[[AddComment') == 0: newtext += "==== Comment by %s on %s ====\n%s\n\n" % ( authname, to_unicode(time.strftime('%c', time.localtime())), comment) submitted = True newtext += line + "\n" if submitted: page.text = newtext # Let the wiki page manipulators have a look at the # submission. valid = True req.args.setdefault('comment', 'Comment added.') try: for manipulator in WikiModule(self.env).page_manipulators: for field, message in manipulator.validate_wiki_page( req, page): valid = False if field: the_message += tag.div(tag.strong( "invalid field '%s': " % field), message, class_="system-message") else: the_message += tag.div(tag.strong("invalid: "), message, class_="system-message") # The TracSpamfilterPlugin does not generate messages, # but throws RejectContent. except TracError, s: valid = False the_message += tag.div(tag.strong("ERROR: "), s, class_="system-message") if valid: page.save(authname, req.args['comment'], req.environ['REMOTE_ADDR']) # We can't redirect from macro as it will raise RequestDone # which like other macro errors gets swallowed in the Formatter. # We need to re-raise it in a post_process_request instead. try: self.env.log.debug( "AddComment saved - redirecting to: %s" % page_url) req._outheaders = [] req.redirect(page_url) except RequestDone: req.addcomment_raise = True else: the_message = tag.div( tag.strong("ERROR: "), "[[AddComment]] " "macro call must be the only content on its line. " "Could not add comment.", class_="system-message") the_form = tag.form( tag.fieldset( tag.legend("Add comment"), tag.div((wikipreview and "Page preview..." or None), tag.textarea((not cancel and comment or ""), class_="wikitext", id="addcomment", name="addcomment", cols=80, rows=5, disabled=(not cancomment and "disabled" or None)), class_="field"), (req.authname == 'anonymous' and tag.div( tag.label("Your email or username:"******"authoraddcomment"), tag.input(id="authoraddcomment", type="text", size=30, value=authname, name="authoraddcomment", disabled=(not cancomment and "disabled" or None))) or None), tag.input(type="hidden", name="__FORM_TOKEN", value=req.form_token), tag.div(tag.input(value="Add comment", type="submit", name="submitaddcomment", size=30, disabled=(not cancomment and "disabled" or None)), tag.input(value="Preview comment", type="submit", name="previewaddcomment", disabled=(not cancomment and "disabled" or None)), tag.input(value="Cancel", type="submit", name="canceladdcomment", disabled=(not cancomment and "disabled" or None)), class_="buttons"), ), method="post", action=page_url + "#commenting", ) if not wikipreview: # Wiki edit preview already adds this javascript file add_script(req, 'common/js/wikitoolbar.js') return tag.div(the_preview, the_message, the_form, id="commenting")
def AddComment(macro, environ, data, *args, **kwargs): """Display an add comment form allowing users to post comments. This macro allows you to display an add comment form on the current page allowing users to post comments. The comments are added to the page's content itself. **Arguments:** //No Arguments// **Example(s):** {{{ <<AddComment>> }}} <<AddComment>> """ # Setup info and defaults parser = environ.parser request = environ.request page = data["page"] page_name = page["name"] page_text = page["text"] # Get the data from the POST comment = request.kwargs.get("comment", "") action = request.kwargs.get("action", "") author = request.kwargs.get("author", environ._user()) # Ensure <<AddComment>> is not present in comment, so that infinite # recursion does not occur. comment = re.sub("(^|[^!])(\<\<AddComment)", "\\1!\\2", comment) the_preview = None the_comment = None # If we are submitting or previewing, inject comment as it should look if action == "preview": the_preview = tag.div(tag.h1("Preview"), id="preview") the_preview += tag.div(parser.generate(comment, environ=(environ, data)), class_="article") # When submitting, inject comment before macro if comment and action == "save": new_text = "" comment_text = "\n==== Comment by %s on %s ====\n\n%s\n\n" % ( author, time.strftime('%c', time.localtime()), comment) for line in page_text.split("\n"): if line.find("<<AddComment") == 0: new_text += comment_text new_text += line + "\n" search = environ.search storage = environ.storage storage.reopen() search.update(environ) storage.save_text(page_name, new_text, author, "Comment added by %s" % author) search.update_page(environ.get_page(page_name), page_name, text=new_text) the_comment = tag.div(parser.generate(comment_text, environ=(environ, data)), class_="article") the_form = tag.form( tag.input(type="hidden", name="parent", value=page["node"]), tag.fieldset( tag.legend("Add Comment"), tag.p( tag.textarea( (not action in ("cancel", "save") and comment or ""), id="comment", name="comment", cols=80, rows=5 ), class_="text" ), tag.h4(tag.label("Your email or username:"******"author")), tag.p( tag.input(id="author", name="author", type="text", value=(not action in ("cancel", "save")) and author or "" ), class_="input" ), tag.p( tag.button("Preview", type="submit", name="action", value="preview"), tag.button("Save", type="submit", name="action", value="save"), tag.button("Cancel", type="submit", name="action", value="cancel"), class_="button" ), ), method="post", action="" ) return tag(the_preview, the_comment, the_form)