Multiple WTForm fields from same form class in HTML table? - python

I'm struggling to figure out how to accomplish unique form field instances that are associated with another element in a table. For a contrived example, here's the following code:
from flask import Flask, render_template, request
from flask.ext.wtf import Form
from wtforms import TextField, BooleanField
app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'secret'
class Person(Form):
fname = TextField('First Name')
phone = TextField('Phone Number')
email = TextField('Email Address')
active = BooleanField('Active')
#app.route("/", methods=['GET', 'POST'])
def guest():
names = ['Bob', 'Joe', 'Jason', 'John'] # this could be a list, or data returned from a db query, previous POST data, or whatever
form = Person()
if request.method == 'POST' and form.validate_on_submit():
return str(form.data)
return render_template('index.html', names=names, form=form)
if __name__ == "__main__":
app.run(host='0.0.0.0')
My template:
<html>
<body>
<form action="" method="post">
<table border="1">
<thead>
<tr>
<th>Name</th>
<th>Telephone Number</th>
<th>Email Address</th>
<th>Active</th>
</tr>
</thead>
<tbody>
{% for n in names %}
<tr>
<td>{{ n }}</td>
<td>{{ form.phone }}</td>
<td>{{ form.email }}</td>
<td>{{ form.active }}</td>
</tr>
{% endfor %}
</tbody>
</table>
It's obvious that in the HTML, the name parameters for each input element in each table row are all identical, because the same form field is being used, so on "POST", if one enters a phone number, that phone number is now populated for all the name rows in the table. Same goes for the "Activate" checkbox. I've also tried generating a list of form instances and passing that list into the template, but it doesn't appear to be working as I expected it would. It would seem as though my options at this point are:
Don't use WTForms for this (see note below)
Use the WTForms FieldList + FormField (I don't think this would work either as these require named keys vs arbitrary keys so I'd run into the exact same issue.)
Use HiddenFields in some fashion (Haven't even thought of how this would work).
I'm aware that I can just create the <input type> elements into the table manually via the template and assign them different names/id's based on w but this doesn't feel very idiomatic, plus I'd have to code in the validation on my own.

You normally would pass in a multi-dict object containing the form data as the first argument to the form:
form = UpdateWidgetForm(request.form)
this does require that the POST data matches the widgets in the form; each widget will inspect that multi-dict and pull out what it needs to render with some pre-populated data.
Alternatively, you can pass in values as keyword arguments, but that does require you've validated the input yourself, converting any values to the right types:
number = request.form.get('widget_number')
try:
number = int(number)
except ValueError:
abort(404)
policy = request.form.get('widget_policy')
if policy not in ('foo', 'bar'):
abort(404)
form = UpdateWidgetForm(widget_number=number, widget_policy=policy)
See How Forms get data in the documentation.
If you need to generate multiple forms within a page, each unique, from the same form object, you need to give them each a unique prefix with the Form(prefix=..) argument.

Related

Loop through items before rendering pdf and populating table using pdfkit and Django

I'm trying to export all of my users and data related to them in a rendered pdf. I'm struggling to loop over all my users and creating new table rows for each user. As per now the loop loops over all users, but it only populates the table with the data of the last user registered.
I do not think I'm able to use Django's conventional way of for looping, at least I cannot figure out how to pass context to the template.
views.py
def users_export(request):
users = User.objects.all()
# Populate template tags in generated pdf with data
data = dict()
for user in users:
data['full_name'] = user.get_full_name
# Getting template, and rendering data
template = get_template('backend/users/users_export.html')
html = template.render(data)
pdf = pdfkit.from_string(html, False)
# Function for creating file name
# Inner function
def create_file_name():
file_name = 'users %s.pdf' % (timezone.now())
return file_name.strip()
filename = create_file_name()
response = HttpResponse(pdf, content_type = 'application/pdf')
response['Content-Disposition'] = 'attachment; filename="' + filename + '"'
return response
users_export.html
<table>
<tr class="thead">
<th class="text-left">Name</th>
</tr>
<tr>
<td class="text-left">{{ full_name }}</td>
</tr>
</table>
You are looping the users correctly, but saving it in the dictionary the wrong way.
You are overriding the full_name key each time. You should use an unique value from the user as key and the full name as value
data = dict()
for user in users:
user[user.username] = user.full_name
Also, I don't know about pdfkit, but assuming it's similar to Jinja or django template, you will need to loop through all the items.
Another option is to simply pass a list of full names instead of a dictionary
data = [user.full_name for user in users]
Edit:
Generate you also need to change your template and how you pass the data to the render function.
Try this:
html = template.render({"users": data})
Template
<table>
<tr class="thead">
<th class="text-left">Name</th>
</tr>
{% for user in users %} // this is assuming that you are using the list
<tr>
<td class="text-left">{{ user }}</td>
</tr>
{% endfor %}
</table>

I am trying to pass input from an input field into my flask app

i am looping through data in a Jinja template and I have set the id component of each record as the input for an input field. Each input field has a corresponding submit button. I am trying to send the data from the input field to my Flask app by clicking the submit button but nothing works.
I have tried setting the method to GET and then and then using delete = request.args.get('delInput') in my Flask app but that does not work. I have used various forms of delinput.
<tbody>
<ul>
{%for id in records%}
<tr>
<th scope="row">{{loop.index}}</th>
<td>{{id[0]}}</td>
<td>{{id[1]}}</td>
<th>
<form method = 'POST'>
{{setDel.hidden_tag()}}
{{setDel.delInput(value = id[2])}}
{{setDel.delButton(class="btn btn-danger")}}
</form>
</th>
</tr>
{%endfor%}
</tr>
</tbody>
elif setDel.validate_on_submit():
delete = setDel.delInput.data
with open('/home/matt/Desktop/test.txt','w') as x:
x.write(str(delete),'testing')
The syntax is not correct. First of all for a form to pass data every field needs to have 'name' attribute set.
Secondly, id[0].. is invalid. Either your fields have a key like {{id['key1']}} or if not you use {{id.0}}, {{id.1}} etc.
The id name in for loop might trick you.

Pass data from textbox to wtform & display in browser

EDITED. My original question wasn't clear enough. I want to allow a user to pass values into a TextField in wtforms, then the value they entered appears after they add it. This would allow the user to pass multiple values before then hitting a final "Sumbit" button on all the values that were originally entered.
I found this question for doing something with the entered text, which is what I tried.
My Python code:
from flask import Flask, request, render_template, redirect
from wtforms import TextField, Form, SubmitField
def redirect_url(default='index'):
return request.args.get('next') or \
request.referrer or \
url_for(default)
class RegionForm(Form):
field = TextField('Region')
Submit = SubmitField('AddRegion')
fieldList = []
def main():
app = Flask(__name__)
#app.route('/region/', methods=['GET'])
def region():
form = RegionForm(request.form)
return render_template("region.html",
form=form)
#app.route('/add/', methods=['POST'])
def add():
request.form['fieldList'].append(request.form['field'])
return redirect(redirect_url())
app.run(debug=True)
My html code:
<form action="/add/" method="Post">
{% for field in form %}
<tr>
<th>{{ field.label }}</th>
<td>{{ field }}</td>
</tr>
{% endfor %}
</form>
{% for item in form.fieldList %}
{{ item }}
{% endfor %}
But after I enter the text and click the "AddRegion" button, I get the following error: The browser (or proxy) sent a request that this server could not understand. However, if I comment out the line request.form['fieldList'].append(request.form['field']), then the redirect happens, but the text hasn't been added to the hidden list on the form. How do I both add the text to the list and redirect back to the original page, so the user can add more text? It seems like there must be an error with this line only, because the rest works fine.
How can I allow a user to dynamically add text to a field, then have that field display in the browser?
Then once the complete region fields have been added, I want to be able to retrieve that list to process in a separate function later.
Part One
So after looking at your code, I think I have found your problem. Flask is very particular about its app routes.
The app route that you have in your flask is:
#app.route('/add', methods=['POST'])
However, your current action on your form which is:
<form action="/add/" method="Post">
In flask /add and /add/ are actually two different web-routes. Therefore, the #app.route is not being triggered. If you change your code to:
`<form action="/add" method="post">`
You should be good to go from here.
Part Two
I think I may have an additional issue. So within your HTML right now, you actually close your </form> tag before looping through your items in the fieldList.
</form>
{% for item in form.fieldList %}
{{ item }}
{% endfor %}
Try:
{% for item in form.fieldList %}
{{ item }}
{% endfor %}
</form>
What I believe to be happening is that your form inputs are not actually being placed inside of the form so when you try to access them you are getting a KeyError.
I second what Cody Myers said. However there's a simple way to guarantee correct routes even if you later change them: in your form use action="{{ url_for('region') }}" and Flask will automatically substitute the correct route for the given function name.

Ordering queryset client-side, Django + TwitterBoostrap

I have the following:
models.py
class Person(models.Model):
full_name = models.CharField(...)
address = models.CharField(...)
views.py
def all_persons(request):
persons = Person.objects.all()
return render(
request,
'my_template.html',
{
'persons':persons
}
)
my_template.html
<table class="table table-striped">
<thead>
<tr>
<th>Person's Fullname</th>
</tr>
</thead>
<tbody>
{% for person in persons %}
<td>{{ person.full_name }}</td>
<td>{{ person.address }}</td>
Ok, so the code as it is will display an unordered list of persons in the template, of course I can manage to order by Person's name or address, I want to give the user the possibility to order by any field, I want to accomplish this with a button on each column of the table in the template. For example a button on top of full_name and a button on top of address, if any of those is pressed, then the list should be ordered depending on the pressed button.
Thank you very much for reading, hope you can help me.
Edit: I use Twitter Boostrap 2, so tablesorter and DataTables JS wont work.
Check out this JS plugin for HTML tables: Datatables. Comes with sorting, filtering, pagination, lots of plugins. Easy to get started, but you might consider it a bit bloated.
Another way is to have the view take an extra argument (an integer representing a field, for example), sort the queryset by this field and render the template.

How to render django form field in template

I want to make a page with a list of users and checkboxes that signal if a user is selected, which will apply some action to selected users.
I created a form class which looks like this:
#in forms.py
class UserSelectionForm(forms.Form):
"""form for selecting users"""
def __init__(self, userlist, *args, **kwargs):
self.custom_fields = userlist
super(forms.Form, self).__init__(*args, **kwargs)
for f in userlist:
self.fields[str(f.id)] = forms.BooleanField(initial=False)
def get_selected(self):
"""returns selected users"""
return filter(lambda u: self.fields[str(u.id)], self.custom_fields)
In my template I have users listed in a table and I want the last column of this table to be those checkboxes. I need to render fields one by one depending on their name.
I tried creating a template tag that would return the html code of the needed form element:
#in templatetags/user_list_tags.py
from django import template
register = template.Library()
#this is django template tag for user selection form
#register.filter
def user_select_field(form, userid):
"""
returns UserSelectionForm field for a user with userid
"""
key = std(userid)
if key not in form.fields.keys():
print 'Key %s not found in dict' % key
return None
return form.fields[key].widget.render(form, key)
Finally, here's the template code:
<form action="" method="post">
{% csrf_token %}
<table class="listtable">
<tr>
<th>Username</th>
<th>Select</th>
</tr>
{% for u in userlist %}
<tr>
<td>{{u.username}}</td>
<td>{{select_form|user_select_field:u.id}}</td>
</tr>
{% endfor %}
</table>
<p><input type="submit" value="make actions" /></p>
However, this does not bind those widgets to the form and thus, after submitting the form, validation fails. The error message says that all the custom fields are required.
So here are my questions:
What is the right way to render separate form fields?
What is the right way of creating such a form with checkboxes? (I mean maybe my method is stupid and there is a much easier way of achieving what I want.
You're making the template far too complicated. Add a label to each field when you create it in the form's __init__ method.
for f in userlist:
self.fields[str(f.id)] = forms.BooleanField(label=f.username, initial=False)
Then just loop over the fields in the form and don't worry about the userlist anymore.
{% for field in form %}
<tr>
<td>{{ field.label_tag }}</td>
<td>{{ field }}</td>
</tr>
{% endfor %}
Ok So I think I have found a way to correctly render separate form fields. I found it watching django sources. Django.forms.forms.BaseForm class has _html_output method which creates an instance of Django.forms.forms.BoundField and then adds unicode(boundField) to the html output. I did the exact same thing and it worked perfectly:
#in templatetags/user_list_tags.py
from django import template
from django import forms
register = template.Library()
#this is djangp template tag for user selection form
#register.filter
def user_select_field(form, userid):
"""
returns UserSelectionForm field for a user with userid
"""
key = str(userid)
if key not in form.fields.keys():
print 'Key %s not found in dict' % key
return None
#here i use BoundField:
boundField = forms.forms.BoundField(form, form.fields[key], key)
return unicode(boundField)
That generated the same html as {{form.as_p}}, so the POST request will look exactly the same and form will be processed correctly.
I also fixed some mistakes in my form class:
#in UserSelectionForm definition:
...
#__init__
for f in userlist:
self.fields[str(f.id)] = forms.BooleanField(initial=False, required=False)
#get_selected
return filter(lambda u: self.cleaned_data[str(u.id)],
self.custom_fields)
That now seems to work as I planned, without any javascript.

Categories

Resources