I have a page template with a view class. In the page template, I have a button that submits to the same page i.e.
<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 (i.e. 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, whilerequest/getURLmight include acquisition oddities or the wrong URL altogether if your view was included elsewhere:The
filenameinput box tal expression can just use a path expression, these work for dictionaries just fine.You don’t need to redefine the
__init__method, theBrowserViewclass 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:
Because
convert_documentis a method of your view, it can itself accessself.contextandself.request.form['filename'], simplifying your method signature. Since you now know that your form is submitted, you can count onself.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 useget(..., '')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 theredirectmethod through the context, just access it from theself.requestattribute, but remember to return the result: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 aBrowserViewview 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: