I'm trying to submit a form using web2py's ajax function as explained in the book, but using FORM instead of SQLFORM since I don't want the data to be stored in a database.
ajax_test.html
{{extend "layout.html"}}
<h2>Form</h2>
{{=form}}
<script>
jQuery("#myform").submit(function() {
ajax("{{=URL('ajax_test')}}",
["mytext"], "output");
return false;
});
</script>
<h2>Output</h2>
<div id="output"></div>
Controller inside default.py:
def ajax_test():
form = FORM(
INPUT(_type="text", _name="mytext", _id="mytext",
requires=IS_NOT_EMPTY()),
INPUT(_type="submit"),
_id="myform"
)
if request.ajax:
if form.accepts(request.vars, session):
return DIV("Form submitted.")
elif form.errors:
return TABLE(*[TR(k, v) for k, v in form.errors.items()])
else:
return DIV("Couldn't check form.")
return dict(form=form)
When the form is submitted (request.ajax == True), both form.accepts and form.errors return False. Even though I access user's input via request.vars.mytext, then I would need to validate it on my own instead of using web2py's capabilities.
What am I missing?
When you pass the session object to form.accepts(), it will automatically implement cross-site request forgery protection by adding a hidden _formkey field to the form. When the form is submitted, the value of this hidden field is compared with the value stored in the session, and the form is not accepted if the match fails.
There are two problems with your code. First, you only call form.accepts when there is an Ajax request, so the _formkey does not get generated and included in the original form. To correct that, try the following:
form.process()
if request.ajax:
if form.accepted:
form.process() is just a shortcut for form.accepts(request, session). Above, it is called even for the initial non-Ajax call when the form is created. This will generate the _formkey value and store it in the session.
Second, your Javascript code does not submit the whole form but only the mytext field, so the hidden _formkey value will not be posted back to the server. As a result, the form is not accepted (web2py does not display an error in this case, as such a failure would typically be the result of foul play).
I'm not sure it is documented, but the second argument to the ajax() function can instead be a jQuery selector, which will result in all form elements inside the selected element being serialized and submitted. So, try:
ajax('{{=URL('ajax_test')}}', '#myform', 'output');
The #myform selector will result in the entire form being serialized, including the hidden fields included in the form object.
Related
I have a basic view that retrieves some data, renders my page and sends some data to this page:
def myview(request)
one = values.objects.get(user=request.user).address
two = values.objects.get(user=request.user).number
return render(request, "main/mytemplate.html",
context={'address': one, 'numbers': two})
So the values retrieved by those two queries are shown on my page.
Now, on the same page, called mytemplate.html, i'm using another view, which is supposed to handle a form and some other operations:
def secondview(request):
if request.method == 'POST':
if 'button1' in request.POST:
form = MyForm(request.POST)
# check whether it's valid:
if form.is_valid():
profile = form.save(commit=False)
profile.user = request.user
profile.save()
return HttpResponseRedirect(request.path_info)
else:
form = MyForm()
return HttpResponse('it works!')
How can i use the data retrieved by those two queries in the second view? The queries are executed when the page is loaded by the first view. Then, in the same page the second view is used. I want to use the two variables one and two in the second view. Is there a way to do this in Django?
Why don't you make the same queries in the second view? Because i would like the second form to be as fast as possible in terms of reload, without having to do a DB query each time that view is used. Also, since i already retrieved those values when the page is opened, it would be a waste to do that again.
I don't know if this question is clear enough, but the core of it is: can i pass variables/data between two views in django?
You have few options:
Simplest way: include this data in request to the second view (as part of the form data, see an example below). You might even use a single view: if POST was send - store data else do request and show it on a page.
Use cache for that (see an example below) - But I'd recommend to use Django built-in package. Here is a basic example how to use it
Use Django Sessions (see an example below) - it is working option despite of that they have another purpose. When customer is loaded Django will load full session record, so you'll have all data in request.session variable. But that is bad practice: you can get a lot of data duplication and increased database memory consumption.
Use API (e.g. using DjangoRestFramework) together with usual Django app. So you'll just get data you need, and when you need. These API requests can also be cached so it is fast solution.
Yes, you can use session to pass data across views. A session works like a temporary server storage and keeps the needed data in a dictionary form.
For instance, add the following lines to myview:
request.session['one'] = one
request.session['two'] = two
Then, retrieve the data in secondview by referring to the session:
one = request.session['one']
two = request.session['two']
you can use cookies. but if you want more secure your request i suggest to you using redis and the python client for redis
file settings.py
redis = redis.Redis(host='localhost', port=6379, db=0)
file views.py
def view1(request):
redis.set("foo", "boo")
def view2(request):
boo = redis.get("foo")
Why not just saving the results of the two queries as hidden fields in the form rendered by the first template ?
<form ...>
<input type="hidden" id="address" name="address" value="{{address}}">
<input type="hidden" id="numbers" name="numbers" value="{{numbers}}">
...
Then, you can either add 'address' and 'numbers' form fields to MyForm
address = forms.CharField(widget=forms.HiddenInput(), required=False)
...
or just retrieve the values from request.POST
I was wondering if someone could help me.
I want to be able to click on customer and locations be based off of the certain customer, being a dependent dropdown. This information is coming from a database, hence the queries in the following code.
This is my form function for both customer and location
class CustomerPick(SubForm):
customer = QuerySelectField(u'Customer',
get_label=u'sCustomer',
query_factory=lambda :
(TCustomer.query.order_by(TCustomer.sCustomer)),
validators=[DataRequired(),])
location = QuerySelectField(u'Location',
get_label=u'sLocation',
query_factory=lambda :
(TLocation.query.order_by(TLocation.sLocation)),
validators=[DataRequired(),])
Here is the view portion
#route('new/', methods=['GET', 'POST'])
def new(self):
form = CustomerPick()
if form.validate_on_submit():
This is a picture of the dropdown also for reference, if there is anything else needed for you guys to have a go please let me know. Thanks in advance!
Photo
I don't quite get your question but you want to be able to click a user and populate the dropdown based on the location?
This involves some Ajax sending data back and forth.
I'll give you a minimized version of code snippet (not tested).
// front-end - this handles user behavior in dropdown. When a user changes a value
// in a user drop down, it will send a request to your Flask view function.
$("#user_drop_down").change(function () {
let user_identifier = this.value;
$.ajax({
type: "GET",
url:/url_to_flask_view_function/,
data: {user_identifier: user_identifier},
success: function (resp) {
$('#your_designated_div_for_both_dropdowns_div').html(resp.data)
}
});
});
# back-end - this receives the request sent from front-end and process the Flask-WTF
# form options so that it can render different options in the dropdowns. In this view
# function, we will return jsonified template with newly processed form_object
# instead of rendering the option data. You can return the json with only the data
# but it involves more javascript parsing and may be difficult for you.
#app.route('/url_to_flask_view_function/')
def form_processing():
user_identifier = request.args.get('user_identifier)
# now we've gotten the user_identifier, you will need to query. Below query is totally made up.
query_to_where_location = Location.query.filter(Location.user_identifier= user_identifier).first()
# your query result will not be in a tuple format
# if your query result is like this "locA, locB, locC, locD", you need to process
# it so that you make [('locA', 'locA'), ('locB', 'locB').......]
form_object = MyForm()
form_object.location.choices = processed_list
return jsonify({"data":render_template('template_that_contains_your_drodpdowns.html',
form_obj=form_obj)})
<!-- HTML piece, you should've had this already but make sure to specify your fields in HTML following Jinja2 synthax.-->
<form>
{{form_object.user}}
{{form_object.dropdown}}
</form>
In conclusion, the idea here is that you catch user behavior using .change, then based on the change, you will send request with user_identifier to server side. Once it reaches server-side, you will make a query into the DB and render the same template again with differently processed forms.
The best way to go about doing this is that, once you get the user_identifier into your view, you make query and return jsonified location object, then in your success block, you would alter the of that dropdown input element.
Let me know if you have more questions.
I'm new to both web development and django so maybe that's a noob question.
I want to do the following:
Ask user to fill some form and submit it.
Then, parse and format the content and display it back to the user to let him verify it.
User can accept the result or go back to the previous view, update data and resend.
This is as far as I can think:
views.py
def add_content(request):
if request.method == 'POST':
form = AddContentForm(request.POST)
if form.is_valid():
content = form.save(commit=False)
return verify_content(request, content)
else:
form = AddContentForm()
return render(request, 'myapp/add_content.html', {'form' : form})
def verify_content(request, content):
return render(request, 'myapp/verify_content.html', {'content' : content})
The verify_content template will obviously contain two buttons ('back', 'ok'), but I don't know how to pass the content object to a view for saving it in the db, or send it back to the previous view from there. Should I use js? Can i do it with just server side code?
Maybe my whole logic is wrong. Should I save the object in the db before verification and then delete it if needed (sounds ugly)? What is a good way to implement this?
Thanks in advance for your time.
You could use the users session for this:
request.session['content'] = content
and in the view where the user should verify his input do:
content = request.session['content']
and voilá you got the content between 2 views.
Django also secures that users can't tinker with its data by either saving it server side, or in a signed cookie.
I would save the form with commit=True in the add_content view, and would add a verified field or something to the model. Then you can append the pk as GET parameter to the link which will get you back to add_content view from verify. You can extract the parameter from request.GET dict.
I'm using pyramid as my framework to work on a project. Since I used to do a lot of .ASP, whenever a user has given invalid input, I could just assign text to a label( via "runat" attribute then just simply assigning the message on the backend) which will signal to the user that the information they entered is invalid without erasing all their entered data. Is there an equivalent way to do this in python? Ultimately What I'd like to be able to do is report to the user if their data is valid (I could just redirect them to the same page) but, I'd prefer to not have to refresh the screen and have the users fill out the form a second time( or n number of time because of mistakes ).
At the conceptual level, in a "traditional" (non-AJAX) scenario you do re-render the form each time user clicks Submit, there's simply no way around that. Your ASP applications were probably doing the same behind the scenes. The trick is, in case of an error, to re-render the form with the data user has already entered.
Usually it looks like this (assuming you're not using any form libraries and writing your forms by hand)
#view_config(...., renderer='myform.mak')
def my_view(context, request):
if request.method == 'GET':
# display an empty form to the user
return {
'page_title': 'Please fill out this boring form',
'errors': {},
'data': {},
}
elif request.method == 'POST':
# an imaginary method which validates the data submitted by the user
# returns a dict {field_name: error_message}, or None if validation passes
errors = validate_myform(request.POST)
if errors is None:
# save the data and redirect elsewhere
save_myform(request.POST)
return HTTPFound('/')
else:
return {
'page_title': 'Validation error!',
'errors': errors,
'data': request.POST,
}
and in the template you just take care to pre-populate the inputs with data if present:
<input type="text" name="first_name" value="${data.get('first_name, '')}" />
and to render validation messages if the view function passes them:
%if 'first_name' in errors:
<span class="error">${errors['first_name']}</span>
%endif
Form libraries (deform etc.) take care of the plumbing, but the principle is the same - view function receives a POST request, validates data and re-renders the form with the same data and error messages, if any.
When both the GET and POST methods are in the same handler class, and I want to populate form fields with user input after failed form validation, I do this...
Class CommentHandler(BaseHandler):
def get(self, form=None):
if form is None: # create new form unless populated form is passed in
form = CommentForm()
# query DB and create template context
self.render('page.html', **context)
def post(self):
form = CommentForm(self.request.POST)
if form.validate():
# populate entity with form data and save to DB
return self.redirect_to('page')
self.get(form=form) # pass populated form back to user for editing
I don't know if this is the best way to get form data back to the user, but it seems to work. My question is: how do I pass that data back into the form if the GET and POST methods are in different handler classes?
class PageHandler(BaseHandler):
def get(self):
# displays form to user
class CommentHandler(BaseHandler):
def post(self):
# processes POSTed form data...
# but if form.validate() fails,
# how can I pass the form data back to the user
# so they can edit their form input?
When you post and call validate() WTForms binds the form encoded data to the Form instance. An http POST can return a response just like a GET this is why you sometimes get those funny messages in your browser when a server application has failed validation and you try to refresh. Its because the refresh action is going to invoke the GET processing pipeline and you will lose your POST data.
What you need to do instead of delegating back to the get implementation you just need to render a response from your post implementation that passes Form instance with the data bound to it back in the response. If you have set your template up in the recommended way the data will automatically show up in the appropriate fields. Below is a snippet of what your Handler might look like.
def post(self):
form = CommentForm(self.request.POST)
if form.validate():
# populate entity with form data and save to DB
return self.redirect_to('page')
# If we are here it means we failed validation
# We need to send back the data the use supplied
# with error messages so we can re-render the form
# with their data and error messages indicating why
# it was rejected.
self.render('page.html', form=form) # pass populated form back to user for editing
This of course assumes that your page.html knows what to do with the Form instance.
Here's one workflow that may help to solve your problem. I chose to provide a workflow instead of a specific code snippet in order to avoid being too prescriptive in the technology you use to solve the problem.
GET
if session contains form data:
add form data from session to template context
display form
POST
if form validates:
clear this form data from session
save to db
else:
save form data to session
redirect to GET handler