简体   繁体   中英

Handling form submissions in Plone templates

I have a page template with a view class. In the page template, I have a button that submits to the same page ie

<form method="post" tal:attributes="action request/getURL" >
    <input type="hidden" name="filename" value="" tal:attributes="value python:item['filename']" />
    <input type="submit" name="form.action.convert" value="Convert" /> 
</form>

When the form is submitted, the view class is called.

class Html(BrowserView):

    def __init__(self, context, request): 
        self.request = request
        self.context = context         

    def __call__(self):
        # Is this a form submission via POST?
        req = self.request
        if req.get('REQUEST_METHOD', 'POST') and \
            req.form.get('form.action.convert', '') == 'Convert': 

            self.convert_document(self.context, str(req.form.get('filename', '')))


    def convert_document(self, contextObj, fileToConvert):
       """ Do something """
       return None

Now, the problem is I cannot put the logic in the __init__ method since this method is called multiple times, resulting in multiple form submissions with just a single click. However, the __call__ method is called once when the button is clicked, but unfortunately, if I click a link to view the current content item in the view, nothing happens, since the __call__ method is called and nothing happens.

I cannot use the code below in the __call__ method. The browser will complain that the page is redirecting in a manner that will never end.

self.context.REQUEST.response.redirect( self.context.absolute_url() )

Is there a BETTER way of handling form submissions in a page template in Plone? How do I call my method (ie convert_document ) in the view class from the page template?

First, some comments on your code, it can be simplified and improved:

Best practice is for your form to use the absolute_url() method of your view; it's the canonical URL, while request/getURL might include acquisition oddities or the wrong URL altogether if your view was included elsewhere:

<form method="post" tal:attributes="action view/absolute_url" >
    <input type="hidden" name="filename" value="" tal:attributes="value item/filename" />
    <input type="submit" name="form.action.convert" value="Convert" /> 
</form>

The filename input box tal expression can just use a path expression, these work for dictionaries just fine.

You don't need to redefine the __init__ method, the BrowserView class already provides that for you.

To test if a form has been submitted, we generally just test for the submit button being present in the request, your test is more elaborate than is needed:

def __call__(self):
    if 'form.action.convert' in self.request.form: 
        self.convert_document()

Because convert_document is a method of your view, it can itself access self.context and self.request.form['filename'] , simplifying your method signature. Since you now know that your form is submitted, you can count on self.request.form['filename'] to exist and be a string; if it isn't someone has been manually tinkering with the request and things deserve to break, so I generally do not use get(..., '') in such cases.

Views must always do all their work in the __call__ method, since the __init__ is called during traversal, at which time such crucial information as the current user have not yet been determined. You do not need to access the response for the redirect method through the context, just access it from the self.request attribute, but remember to return the result:

return self.request.response.redirect(self.context.absolute_url())

However, if your view is the default view of the context object, that will indeed lead to a redirect loop that the browser will not tolerate.

Note that if you do override the __call__ method of a BrowserView view class that has a template associated with it, you need to make sure you return the output of the template where appropriate. My stab at your view class would thus be:

class Html(BrowserView):
    def __call__(self):
        if 'form.action.convert' in self.request:
            self.convert_document()
            return self.request.response.redirect(self.context.absolute_url())
        return self.index()

    def convert_document(self):
       """ Do something """
       context = self.context
       filename = self.request.form['filename']
       # Do something with the context and filename

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM