WTforms form not submitting but outputs no validation errors - python

I'm trying to get file uploads with flask-uploads working and running in to some snags. I'll show you my flask view function, the html and hopefully someone can point out what I'm missing.
Basically what happens is that I submit the form and it fails the if request.method == 'POST' and form.validate(): check in the view function. It jumps down to display the template. wtforms isn't kicking me any errors on the form so I'm wondering why its failing that if statement.
What am I over looking?
Setting up flask-uploads:
# Flask-Uploads
photos = UploadSet('photos', IMAGES)
configure_uploads(app, (photos))
View:
def backend_uploadphoto():
from Application import photos
from Application.forms.backend import AddPhotoForm
clients = Client.query.all()
events = Event.query.order_by('date').all()
form = AddPhotoForm(request.form, csrf_enabled=True)
if request.method == 'POST' and form.validate():
from uuid import uuid4
uuid = uuid4()
filename = '{0}.jpg'.format(uuid)
photo = Photo(uid=uuid, client=request.form['client'], event=request.form['event'])
photofile = photos.save(request.files.get('photo'), photo.filename)
return redirect(url_for('backend'))
return render_template('backend/addphoto.html', form=form, clients=clients, events=events)
Form:
class AddPhotoForm(Form):
photo = FileField('Photo')
client = IntegerField('Client:')
event = IntegerField('Event:')
HTML:
<form action="{{url_for('backend_uploadphoto')}}" method="post">
<p>
{{form.client.label}}
<select name="client">
{% for client in clients %}
<option value="{{client.id}}">{{client.fullname}}</option>
{% endfor %}
</select>
{{form.client.errors}}
</p>
<p>
{{form.event.label}}
<select name="event">
{% for event in events %}
<option value="{{event.id}}">{{event.name}}</option>
{% endfor %}
</select>
{{form.event.errors}}
</p>
<p><label for="photo">Photo:</label>{{form.photo}} <input type="submit" value="Upload"> {{form.photo.errors}}</p>
</form>

You have csrf_enabled=True but your form doesn't have any CSRF protection since you aren't inheriting from SecureForm. If you want to enable CSRF, read the documentation and update your form definition.
If this was unintended, you can remove csrf_enabled=True and your logic will work as expected.
To enable CSRF protection, there are a few steps:
Inherit from SecureForm
Create the generate_csrf_token and validate_csrf_token methods in your form. These methods will generate a unique key and raise errors when it doesn't validate.
Add {{ form.csrf_token }} to your template.

Related

How to solve blank page after posting the form problem?

I want to send a basic form to views. I create everything that it need but when I submit the post, it doesn't send a return a blank page.
This is my form and app:reports is the same page with form, because I want to return the same page. I need the values of year_one and year_two.
<form method="post" action="{% url 'app:reports' %}">
{% csrf_token %}
<label for="year_one">Select year 1:</label>
<select id="year_one" name="year_one">
{% for case in query_trend %}
<option value="{{case.date_created__year}}" >{{case.date_created__year}}</option>
{% endfor %}
</select>
<label for="year_two">Select year 2:</label>
<select id="year_two" name="year_two">
{% for case in query_trend %}
<option value="{{case.date_created__year}}">{{case.date_created__year}}</option>
{% endfor %}
</select>
<button onclick="test()">click</button>
</form>
And this is my view
if self.request.method == 'POST':
year_one = self.request.GET.get('year_one')
year_two = self.request.GET.get('year_two')
return HttpResponseRedirect('fdm:outstanding_reports')
What should I do for using these values in views?
Assuming your button's test() method is successfully submitting the form...If you are passing the request to your view,ie,
def reports(request):
Then you can refer to the posted values of year_one and year_two as:
if request.method == 'POST':
year_one = request.POST.get('year_one')
year_two = request.POST.get('year_two')
(GET is when you send values contained in the URL, eg page?year_1=1975&year_2=1300, POST is when it happens in the background. A form will generally use only one or the other, so you your version probably wasn't getting any values. Confusingly, you also use the lowercase '.get()' to retrieve the POST values)

How to transfer Checkbox parameters in Django Materialized view?

The setup contains Django with Materialize view library.
Two questions:
1) How to transfer parameters from html to .py -file when using Materialize checkbox?
2) If this works, then how to keep them in a session so that when next time visiting on page, the checkboxes are not empty?
I have googled a lot and made many trials with no success.
Library: href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"
homepage.html:
<form action="#">
{% for sites in wsites %}
<label >
<input type="checkbox" name="option{{sites.id}}" id="option{{sites.id}}" value={{sites.id}}/>
<span>{{sites.name}} </span>
</label>
<br>
{% endfor %}
</form>
views.py
def homepage(request):
if request.method == 'POST':
print(request.POST)
return render(request = request,
template_name='main/home.html',
context = {"wsites":Sites.objects.all })
Session parameters is an empty list, ie. print(request.POST) = []. I would appreciate to get some help how to resolve this.

How to unit test a form submission when multiple forms on a route?

I want to add unit tests to my flask app that tests form behavior on valid and invalid logins + signups. Currently, I have the signup form and a login form hosted on one page and route, and am using a hidden input field to identify which of the two forms is submitted / determine next actions.
My question is - how do I write a unit test that targets a specific form on a page? All the examples I've seen so far post data to a specific route, which is currently what I am doing. But that is failing because I need an added way to say "and we're submitting x form".
So is there a way to add "and we're submitting x form" in the post request?
**
edited to add, here are the different ways I've tried to pass the hidden form data in the post data dict, with no success.
data = dict(username="test#gmail.com", password="test", login_form)
data = dict(username="test#gmail.com", password="test", "login_form")
data = dict(username="test#gmail.com", password="test", login_form=True)
login unit test:
from app import app
import unittest
class FlaskTestCase(unittest.TestCase):
#ensure that login works with correct credentials
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
login route in views.py:
#app.route('/login', methods=['POST', 'GET'])
def login():
login_form = LoginForm()
signup_form = SignupForm()
error_login = ''
error_signup = ''
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
return render_template('login.html', login_form=login_form, signup_form=signup_form, error=error)
login.html:
<div class="login-form form-400">
<h3>Log In To Your Account</h3>
<form action="" method="post">
<input type="hidden" name="login_form">
{% if error_login != '' %}
<label class="error">
{{ error_login }}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
<input type="submit" value="Login" class="button button-blue">
</form>
</div>
<p class="login-divider">or</p>
<div class="signup-form form-400">
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
{% if error_signup != '' %}
<label class="error">
{{ error_signup | safe}}
</label>
{% endif %}
{% from "_formhelper.html" import render_field %}
{{ signup_form.hidden_tag() }}
{{ render_field(signup_form.username, placeholder="Pick a Username", class="form-item__full") }}
{{ render_field(signup_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(signup_form.password, placeholder="Create a Password", class="form-item__full") }}
<input type="submit" value="Sign Up" class="button button-green">
</form>
</div>
Ok I figured it out. To pass the login_form info, I had to end up passing an empty string on the login_form like this:
def test_correct_login(self):
tester = app.test_client(self)
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test", login_form=""),
follow_redirects=True
)
self.assertIn(b'you are logged in', response.data)
I did this by throwing a print request.form in my views.py for this route and then saw the empty string.
It was still failing, but because the login_form.validate() was failing because of the csrf token added by the WTForms module. In the end, this discussion had the answer.
Flask-WTF / WTForms with Unittest fails validation, but works without Unittest
Thanks #drdrez for your suggestions!
Update:
Thanks for updating your question with what you've already tried! I have a few other ideas about what's causing the issue.
Let's continue to look at the HTML and try to understand the technologies your program is built on top of. In the server side login.html file, notice these lines:
{% from "_formhelper.html" import render_field %}
{{ login_form.hidden_tag() }}
{{ render_field(login_form.email, placeholder="Your Email", class="form-item__full", type="email") }}
{{ render_field(login_form.password, placeholder="Your Password", class="form-item__full") }}
It isn't HTML, and is probably being processed on the server side to produce HTML and serve to the client. The line that contains login_form.hidden_tag() looks interesting, so I would recommend loading this page in your browser and inspecting the HTML served to the client. Unfortunately, I haven't used Flask before, so I can't give any more direct help.
However, my advice is to continue digging into how Flask and the HTML Form works. The nice thing about Python is you have access to libraries' source code, which allows you to figure out how they work so you can learn how to use them and fix bugs in your application that uses them.
Sorry I can't give you more direct help, good luck!
Let's look at login.html. When you submit a form, how does the login route in views.py know which form was submitted? If you know HTML Forms, <input> elements nested in a form are used to, in this case, post data to your server/application.
Back to login.html, notice these two lines:
...
<h3>Log In To Your Account</h3>
<input type="hidden" name="login_form">
...
<h3>Create a New Account</h3>
<form action="" method="post">
<input type="hidden" name="signup_form">
...
Those are <input> elements, with a type of "hidden", so they won't display, with names of "login_form" and "signup_form", which are included in the data that is submitted by the form.
Now in the login route in views.py, you'll notice there two lines:
#login form
if 'login_form' in request.form and login_form.validate():
# do login form stuff
#signup form
if 'signup_form' in request.form and signup_form.validate():
# do signup form stuff
Those are testing to see if the phrase "login_form" or "signup_form" are in present in the list request.form. Back to your unit test now:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True
)
Notice the data you are passing in the dict, this is mimicking the form data, so you should probably include either "login_form" or "signup_form" to mimic the behavior of the HTML form correctly.
If you're unfamiliar with HTML Forms and HTTP Post, I would suggest searching for some tutorials, or just reading documentation on MDN or elsewhere. When building software on top of a technology (like HTTP and HTML), it can be helpful to understand how those technologies work when you run into bugs in your own software.
Hope this helps, let me know if I can clarify anything!
You might be experiencing a problem because you have not flagged the request as being of the form application content type. I note you are trying to access request.form, which requires that the data package is parsed in a certain way. You could try to do something like the following:
response = tester.post(
'/login',
data = dict(username="test#gmail.com", password="test"),
follow_redirects=True,
headers = {"Content-Type":"application/x-www-form-urlencoded"}
)

Using Django FormPreview the right way

My Goal
I have a django project with a form, and I want to display a preview page before the user submits.
The problem
I can display a preview page using a Django FormPreview, but not all form data is displayed properly. Specifically, if I have a field with choices, the string values of these choices aren't displayed. I'm also having problems applying template filters to date fields. The end result is that some data on the preview page is visible but other data is blank:
However, if I display the same data for posts that have actually been submitted, then everything displays properly:
My Code
models.py:
class Game(models.Model):
# Game Choices
FOOTBALL = 0
BASKETBALL = 1
TENNIS = 2
OTHER = 3
GAME_CHOICES = (
(FOOTBALL, 'Football'),
(BASKETBALL, 'Basketball'),
(TENNIS, 'Tennis'),
(OTHER, 'Other')
)
game_id = models.AutoField(primary_key=True)
location = models.CharField(max_length=200, verbose_name="Location")
game = models.IntegerField(choices=GAME_CHOICES, default=FOOTBALL)
game_date = models.DateField(verbose_name='Game Date')
forms.py
class GameForm(ModelForm):
class Meta:
model = Game
fields = (
'location',
'game',
'game_date'
)
I'm pretty sure that the problem is in my views.py: I'm not sure that I'm processing the POST request the right way to feed all data to the preview page.
views.py
def form_upload(request):
if request.method == 'GET':
form = GameForm()
else:
# A POST request: Handle Form Upload
form = GameForm(request.POST) # Bind data from request.POST into a GameForm
# If data is valid, proceeds to create a new game and redirect the user
if form.is_valid():
game = form.save()
return render(request, 'games/success.html', {})
return render(request, 'games/form_upload.html', {
'form': form,
})
preview.py
class GameFormPreview(FormPreview):
form_template = 'games/form_upload.html'
preview_template = 'games/preview.html'
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/games/success')
form_upload.html
...
<form method="post">
{% csrf_token %}
<ul><li>{{ form.as_p }}</li></ul>
<button type="submit">Preview your post</button>
</form>
...
preview.html
{% load humanize %}
...
<h1>Preview your submission</h1>
<div>
<p>Location: {{ form.data.location }}</p>
<p>Game Date: {{ form.data.game_date|date:"l, F d, Y" }}</p>
<p>Game Type: {{ form.data.get_game_display }}</p>
</div>
<div>
<form action="{% url 'form_upload' %}" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
<!-- Submit button -->
<button type="submit">Submit your post</button>
<!-- Go back button -->
<button type="submit">
<a href="{% url 'form_upload' %}"
onClick="history.go(-1);return false;" >
Go back and edit your post
</a>
</button>
</div>
</form>
</div>
...
Two issues
Essentially, I'm having these two issues:
String values for choices are not displayed. If I use the get_FOO_display() method in my preview.html template, it returns blank. However, if I use this in a page after the post has been submitted, it displays properly.
The humanize date filter doesn't work. If I apply a humanize filter ({{ form.data.game_date|date:"l, F d, Y" }}) in preview.html, it also displays blank. Again, this works for submitted posts.
My question essentially is: what's the right way to use the FormPreview here?
form.data does not have get_FOO_display attributes. When you access {{ form.data.get_game_display }} in the template, it fails silently and doesn't display anything.
The get_FOO_display are methods of the instance, so try this instead.
{{ form.instance.get_game_display }}
Wherever possible you should access data from form.cleaned_data (which is validated and 'cleaned') instead of form.data, which is the raw data submitted to the form.
The filters don't work with form.data.game_date because it's a raw string. They should work with form.cleaned_data.game_date, which has been converted to a python date object.
Finally, you haven't implemented anything in your done method, you've just copied the comment from the docs. You could create a new game using cleaned_data as follows:
def done(self, request, cleaned_data):
game = Game.objects.create(**cleaned_data)
return HttpResponseRedirect('/games/success')

google app engine with django upload file error

i am using django with google app engine . i am trying to upload images.
i made a form
<form enctype="multipart/form-data" action="addImage" method="post">
<p>Title of the Image:
<input type="text" name="title" /></p>
<p>Please select image to upload:
<input type="file" name="img" required="True"/></p>
<p><input type="submit" value="Upload" /></p>
</form>
mapping it to this view
def addImage(request):
image = Image()
image.title = request.POST.get("title")
img = images.resize(request.POST.get('img'),50,50)
image.blob = db.Blob(img)
image.put()
return HttpResponse('<html><head><meta HTTP-EQUIV="REFRESH" content="2; url=/"></head><body>One item added successfuly </body></html>')
its giving me this error in the debugging session
Exception Type: NotImageError
Exception Value:Empty image data.
WHY?????
I haven't used Google App Engine, but this is how I would do it on a pure Django 1.3 installation:
forms.py:
from django import forms
from django.forms import fields
class UploadImageForm(forms.Form):
image_file = fields.ImageField()
views.py:
from django.shortcuts import render_to_response
from django.template import RequestContext
from NAME_OF_YOUR_APP.forms import UploadImageForm
def addImage(request):
if request.method == 'POST':
upload_image_form = UploadImageForm(data=request.POST, files=request.FILES)
if upload_image_form.is_valid():
image_file = request.cleaned_data['image_file']
# do something with the image...
return ...
else:
upload_image_form = UploadImageForm()
context = {'form':upload_image_form}
return render_to_response('path/to/upload_template.html', context, context_instance=RequestContext(request))
upload_template.html:
<form enctype="multipart/form-data" action="" method="post">
{% csrf_token %}
<table>
<tr>
<td>{{ form.image_file.label_tag }}</td>
<td>{{ form.image_file }}</td>
<td>{% if form.image_file.errors %}{% for error in form.image_file.errors %}{{ error }}{% endfor %}{% endif %}</td>
</tr>
</table>
<input type="submit" value="Submit"/>
</form>
Your template code looks good (it's missing {% csrf_token %}, which I'm not sure if GAE needs or not). Your view code should check to see if the request is a POST request or not.
In my example, I created a form called UploadImageForm, which accepts a single image_file to be uploaded. Logic works like so:
User visits example.com/upload_image
addImage() runs. Since this is a GET and not a POST request, it makes an empty UploadImageForm(), and renders it inside upload_template.html.
User is displayed the form.
User fills out the form and Submits an image.
Server receives POST request, and addImage() is called again.
We bind the uploaded file data to UploadImageForm.
If there are no errors (e.g. upload_image_form.is_valid() is True), we capture the image_file out of cleaned_data, and we can then do something with it.
If there are errors (upload_image_form.is_valid() is False), the template is re-displayed with error messages.
really simple ,
edit this line:
img = images.resize(request.POST.get('img'),50,50)
with this one:
img = request.FILES['img'].read()
make sure that you are using django 1.2
Try this.. it work for me... :)
def addImage(request):
image = Image()
image.title = request.POST.get("title")
image.blob = db.Blob(str(request.FILES['img']['content']))
image.put()
return HttpResponse('<html><head><meta HTTP-EQUIV="REFRESH" content="2; url=/"></head><body>One item added successfuly </body></html>')

Categories

Resources