Get the clicked submit button in a django form in a generic way - django

I have a finite state machine (django-fsm) which allows an object to go from a source state into one of several target states. I can add all the actions in a dictionary like:
ACTIONS { 'button_1': action1,
'button_2': action2,
...
}
This translates in a form with a submit button for each state.
{% for n,m in object.get_available_current_state_transitions %}
<input type="submit" class="btn" value="{{ n|get_action|capfirst }}"
name="button_{{n}}" />
{%endfor%}
<input type="submit" class="btn primary" value="Save">
<a class="btn" onclick="javascript:history.go(-1)">Cancel</a>
This usually results in more than 3 buttons.
Clicking a button results in a specific action, defined in my case in the model class.
Now, I know I can get the clicked button in the request.POST dictionary, but this would result in a cascade if like:
if 'button_1' in request.POST:
action_1()
elif 'button_2' in request.POST:
...
Is there any way to get the button pressed separately (ideally from the request object) in a variable so I can have something like
ACTIONS[clicked_button_name](...)
? In other words, is there any way to obtain the clicked button outside the POST dictionary?
PS: I've looked other replies on the "multiple buttons" question, but all offer request.POST as answer.

If all of the actions and strings are already in your view, why don't you just iterate over that actions dict?
for key, value in ACTIONS.items():
if key in request.POST:
value()
Just make your button names very unlikely to be used as a regular form field name.
A few alternatives: use javascript to handle the submission and have it set a single form field such as "action".
Use more unique keys and filter through request.POST.keys() with a regex pattern or string comparison.
action = [x for x in request.POST.keys() if 'FAIRLY_UNIQUE_BUTTON_PREFIX' in x]
if action:
ACTIONS[action]()

I don't think so directly, but a couple workarounds could be:
Send your buttons to different urlconfs with some variable (like a three letter arg). All of these confs point to the same view taking this three letter arg as an argument, which then knows what to do. This might still result in a cascade if else though.
Or, send them to different views all together.
You could try doing something ajaxy. The data will still be in a post dict, but you will have more control over how the post dict is structured.
I'm also assuming GET isn't an option for any of these (yet that still results in if else structures.)

Related

CFWheels: Display form errors on redirectto instead of renderpage

I have a form which I am validating using CFWheels model validation and form helpers.
My code for index() Action/View in controller:
public function index()
{
title = "Home";
forms = model("forms");
allforms = model("forms").findAll(order="id ASC");
}
#startFormTag(controller="form", action="init_form")#
<select class="form-control">
<option value="">Please select Form</option>
<cfloop query="allforms">
<option value="#allforms.id#">#allforms.name#</option>
</cfloop>
</select>
<input type="text" name="forms[name]" value="#forms.name#">
#errorMessageOn(objectName="forms", property="name")#
<button type="submit">Submit</button>
#endFormTag()#
This form is submitted to init_form() action and the code is :
public function init_form()
{
title = "Home";
forms = get_forms(params.forms);
if(isPost())
{
if(forms.hasErrors())
{
// don't want to retype allforms here ! but index page needs it
allforms = model(tables.forms).findAll(order="id ASC");
renderPage(action="index");
//redirectTo(action="index");
}
}
}
As you can see from the above code I am validating the value of form field and if any errors it is send to the original index page. My problem is that since I am rendering page, I also have to retype the other variables that page need such as "allforms" in this case for the drop down.
Is there a way not to type such variables? And if instead of renderPage() I use redirectTo(), then the errors don't show? Why is that?
Just to be clear, I want to send/redirect the page to original form and display error messages but I don't want to type other variables that are required to render that page? Is there are way.
Please let me know if you need more clarification.
This may seem a little off topic, but my guess is that this is an issue with the form being rendered using one controller (new) and processed using another (create) or in the case of updating, render using edit handle form using update.
I would argue, IMHO, etc... that the way that cfWheels routes are done leaves some room for improvement. You see in many of the various framework's routing components you can designate a different controller function for POST than your would use for GET. With cfWheels, all calls are handled based on the url, so a GET and a POST would be handled by the same controller if you use the same url (like when a form action is left blank).
This is the interaction as cfwheels does it:
While it is possible to change the way it does it, the documentation and tutorials you'll find seem to prefer this way of doing it.
TL; DR;
The workaround that is available, is to have the form be render (GET:new,edit) and processing (POST:create,update) handled by the same controller function (route). Within the function...
check if the user submitted using POST
if it is POST, run a private function (i.e. handle_create()) that handles the form
within the handle_create() function you can set up all your error checking and create the errors
if the function has no errors, create (or update) the model and optionally redirect to a success page
otherwise return an object/array of errors
make the result error object/array available to view
handle the form creation
In the view, if the errors are present, show them in the form or up top somewhere. Make sure that the form action either points to self or is empty. Giving the submit button a name and value can also help in determining whether a form was submitted.
This "pattern" works pretty well without sessions.
Otherwise you can use the Flash, as that is what it was created for, but you do need to have Sessions working. their use is described here: http://docs.cfwheels.org/docs/using-the-flash and here:http://docs.cfwheels.org/v1.4/docs/flashmessages
but it really is as easy as adding this to your controller
flashInsert(error="This is an error message.");
and this to your view
<cfif flashKeyExists("error")>
<p class="errorMessage">
#flash("error")#
</p>
</cfif>

Submit Button Confusion and Request being sent Twice (Using Flask)

I'm pretty much trying to create a web app that takes 2 svn urls and does something with them.
The code for my form is simple, I'm also using WTForms
class SVN_Path(Form):
svn_url=StringField('SVN_Path',[validators.URL()])
I'm trying to create 2 forms with 2 submit buttons that submit the 2 urls individually so my test3.html looks like this:
<form action="" method="post" name="SVNPath1">
{{form1.hidden_tag()}}
<p>
SVN Directory:
{{form1.svn_url(size=50)}}
<input type="submit" value="Update">
<br>
{% for error in form1.svn_url.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}
</p>
</form>
<form action="" method="post" name="SVNPath2">
{{form2.hidden_tag()}}
<p>
SVN Directory:
{{form2.svn_url(size=50)}}
<input type="submit" value="Update">
<br>
{% for error in form2.svn_url.errors %}
<span style="color: red;">[{{error}}]</span>
{% endfor %}
</p>
</form>
MY FIRST QUESTION is how do I know which submit button was clicked so I can run the proper function on the corresponding svn url. I have tried doing something like
if request.form1['submit'] == 'Update':
if request.form2['submit'] == 'Update':
but that does not work at all. I'm new to web dev in general and flask so a detailed explanation would be helpful.
SECONDLY, since submits weren't working properly I also tried an alternative to keep my work moving so in my .py file I have
#app.route('/test3', methods=['GET','POST'])
def test3():
basepath=createDir()
form1=SVN_Path()
form2=SVN_Path()
if request.method=="POST":
if form1.validate_on_submit():
svn_url = form1.svn_url.data
prev_pdf=PDF_List(svn_url,basepath,'prev') #some function
if form2.validate_on_submit():
svn_url2 = form2.svn_url.data
new_pdf=PDF_List(svn_url,basepath,'new') #some function
return render_template('test3.html', form1=form1, form2=form2)
CreateDir is a function that creates a directory in the local /tmp using timestamps of the local time.
Whenever I go the webpage it creates a directory, lets call it dir1, since its calling CreateDir. Thats what I want, but when I click submit on the form it creates another directory dir2 in the tmp folder which is NOT what I want since I want everything to being the same dir1 directory.
In addition when I put a url in one of the forms and click submit, it automatically puts it the same value in the 2nd form as well.
Sorry if this is really long and possibly confusing, but any help is appreciated.
:) Let's see if we can clarify this a little.
To your first question:
As #dim suggested in his comment, You have a few options:
You can submit your form to separate unique urls. That way you know which form was submitted
You can create two similar but different Form classes (the fields will need different names like prev_svn_url and cur_svn_url). This way in your view function, you instantiate two different forms and you'll know which form was submitted based on form.validate_on_submit()
The third option would be to add a name attribute to your submit button and then change the value attributes to something like 'Update Previous' and 'Update Current'. This way in your view function you can check the value of request.data[<submit button name>] to determine if 'Update Previous' was pressed or 'Update Current'.
To your second question:
Multiple directories are being created because you're calling createDir() each time the page is loaded to show the forms and when the forms get posted. In order to create just once, you'll need some kind of logic to determine that the directory was not previously created before calling createDir()
In addition: Since both forms are from the same SVN_Path class, they read post data exactly the same way, that's why whatever you type in form 1 appears in form 2.
Now for my 2 cents:
I assume you're trying to write some kind of application that takes two SVN urls as input, creates a folder and does something with those URLs in that folder. If this is the case, the way you are currently going about it is inefficient and won't work well. You can achieve this with just one form class having 2 svn_url fields (with different names of course) and then handling all of that in one post.
EDIT: The job of the submit button is to tell the browser that you're ready to send the data on the form to the server. In this case you should only need one submit button (SubmitFiled => when rendered). Clicking that one submit button will send data from both input fields to your view function.
Your form should look something like:
class SVN_Path(Form):
prev_svn_url=StringField('Previous SVN_Path',[validators.URL()])
new_svn_url=StringField('New SVN_Path',[validators.URL()])
and your view function:
def test():
form = SVN_Path()
if request.method == "POST":
if form.validate_on_submit():
basepath = createDir() # Only create dir when everything validates
prev_svn_url = form.prev_svn_url.data
new_svn_url = form.new_svn_url.data
prev_pdf = PDF_List(prev_svn_url, basepath, 'prev')
new_pdf = PDF_List(new_svn_url, basepath, 'new')
...
return render_template('test3.html', form1=form1, form2=form2)

Django: use POST or links, which is better practice?

A noobish question to be sure.
<a href="{% url 'stuff.views.SomeView' %}/somethingnew">
<button>See something new on this page</button>
</a>
<form action="" method="post">{% csrf_token %}
<button name="somethingnew" type="submit" value=True>See something new on this page</button>
</form>
With either choice, I update some boolean variable, perform the appropriate calculations, call the page view and render a page with something new on this page. Part of the reason I use either method is to save the state of a collection of boolean variables. What is the best way 1) change a boolean variable 2) save its state 3) perform the necessary updates when the button is clicked and finally 4) render page after the underlying data has been updated?
Right now, I am using forms rather than links so that I don't need to code a url for each boolean variable. Which method is better? Will one method improve the time it takes to reload the page (assuming many boolean variables)?
1) Following the REST mindset, a POST request is in order to transmit the user input, since you are altering database objects.
2) I'd save it in the Session object if the input is not needed forever (session duration). Otherwise in the database as you are doing now.
3/4) I'd gather all the necessary info in a form. When the user commits the form in a POST request, I'd compute the data and respond with the rendered page containing the computed result. If the input variables are gathered step by step with intermittent computation, I'd just update the input form accordingly (display different choices in a combo box or something like that). Of course the transmitting could be done in an AJAXy way, too.

Django -- pass "next" parameter to cancel button?

I know how to pass a next get parameter to a view so that when the view redirects, it goes to whatever url is in next. Is it possible to do this with the cancel button of a form?
If my form buttons are like this:
<input type="submit" value="Save Changes"><input type="button" value="Cancel Changes" onclick="window.location.href='/systems/'">
Is there some way to add the next parameter into the href instead of /systems/?
Thanks!
Well, this is just HTML in your template, so why can't you do
onclick="window.location.href='{{ myurl }}'">
?
Edit after comment
Sounds like you need a context processor, which automatically adds the value of the next GET parameter to the context. As simple as:
def get_next(request):
if 'next' in request.GET:
return {'next': request.GET['next']}
Now add this to the list of CONTEXT_PROCESSORS in settings.py, and make sure you use a RequestContext when rendering your template (or just the new render() shortcut).

Redirect to views according to form selection

User registration in my application is performed in steps. After submitting the form, some validation is performed and then register_prompt view is called. It renders a form, with two options - 'ok' and 'cancel'. Clicking ok will run registration, and clicking cancel should redirect to main page. Problem is that no matter which of the two I choose, I'm redirected to .../user/registration/function_1_or_2_name with a blank page (although I have specified url in HttpResponseRedirect ). How can I make it work ?
def register_prompt(request):
context = RequestContext(request)
return render_to_response('user/data_operations/alert.html', context_instance=context)
Form loaded on alert.html :
<form action="" method="post">
<input type="submit" class="submit" name="submit" onClick="this.form.action='{% url register_new %}'" value="Ok" />
<input type="submit" class="submit" name="submit" onClick="this.form.action='{% url redirect_home %}'" value="Cancel" />
</form>
Redirect views (maybe there is a better way to do that ?):
def redirect_home(request):
return HttpResponseRedirect('/')
def register_new(request):
(... registration magic here ...)
return HttpResponseRedirect('/user/registration/complete/')
Finally url conf :
url(r'^register_new/$', register_new, name="register_new"),
url(r'^redirect_home/$', redirect_home, name="redirect_home"),
url(r'^register_prompt/$', register_prompt, name="register_prompt"),
At first I was trying to add some abstract values to form's buttons (like 'action=ok'), and then catch them in register_prompt but it was a total disaster.
I'm not sure if that onClick="" hack is really valid (I'll let someone else weigh in on that), but did you try just using normal links, to see if it's that or something else?
OK
Cancel
(Or check out <button> if you really want it to look like buttons. Or just use two <form>s, each with a separate action="" attributes.)
Hum, other than that... You say ".../user/registration/function_1_or_2_name" -- what exactly does the {% url .. %}s give you? Does it not add the final / (which you require in your patterns)?
At first I was trying to add some
abstract values to form's buttons
(like 'action=ok'), and then catch
them in register_prompt but it was a
total disaster.
But you seem to want to manipulate the action attribute, so you can't catch anything in register_prompt()?
All in all I'd suggest you either use normal links, or actually handle the POST in the view that presented it:
def register_prompt(req):
if 'ok' in req.POST:
return register_new(req)
if 'cancel' in req.POST:
return register_cancel(req)
return render_to_response('user/data_operations/alert.html',
context_instance=RequestContext(req))
Maybe something like that? Renaming your buttons to ok and cancel, and having the form post back to itself...