Multiple instances of the same form field - python

I have invite form with two fields defined as person and email as follows:
class InviteForm(Form):
person = TextField("person", validators=[validators.Required("Please enter persons name.")])
email = EmailField("email", validators=[validators.Required("Please enter valid email."), validators.Email("Please enter valid email.")])
def validate(self):
return validate_form(self)
Where validate_form function is a cusotm validator which check few conditions for invite.
My requirement is to allow users to invite more than one person at a time. To achieve this I have added jquery function which replicates these fields in html form and allow to invite multiple people.
But the problem is in my view function when I extract results from post request it gives only first persons information. How can I get all the persons details. My view is defined as follows:
#app.route("/invite", methods=["GET", "POST"])
def invite():
invite_form = InviteForm()
if invite_form.validate_on_submit():
print invite_form.person
print invite_form.email
This gives only one field, instead of array of fields.
Is this possible with python wtf? How?

What you're looking for is FormField which lets you build a list of the WTF Fields (or groups even).
Here's a simple example-- it'll render three string fields as a html list because that's the minimum required. If you want to add extras via javascript, then just adhere to the same naming scheme, in the case below, a fourth would have a name of person-3.
from flask import Flask, render_template_string
from flask.ext.wtf import Form
from wtforms import FieldList, StringField
app = Flask(__name__)
app.secret_key = 'TEST'
class TestForm(Form):
person = FieldList(StringField('Person'), min_entries=3, max_entries=10)
foo = StringField('Test')
#app.route('/', methods=['POST', 'GET'])
def example():
form = TestForm()
if form.validate_on_submit():
print form.person.data
## [u'One', u'Two', u'Three']
return render_template_string(
"""
<form method="post" action="#">
{{ form.hidden_tag() }}
{{ form.person }}
<input type="submit"/>
</form>
""", form=form)
if __name__ == '__main__':
app.run(debug=True)

Related

store file name in column when upload file using FileField wtforms

I have a table using the following code, which includes two columns: name and filename. My problem is that when I upload the file , then its file name is stored in the filename column. How do I do this?
Now when I upload the file, 'None' is placed in the filename.i can only upload file or enter name in database,i think that problem is enctype="multipart/form-data".
from flask_wtf import FlaskForm
from flask_wtf.file import FileField
from wtforms import StringField,SelectField,IntegerField
from wtforms.ext.sqlalchemy.fields import QuerySelectField
from wtforms.validators import DataRequired, Email, Length
class ContactForm(FlaskForm):
name = IntegerField('name', validators=[Length(min=-1, max=100, message='You cannot have more than 100 characters')])
filename = FileField()
this is my app.py:
def new_contact():
'''
Create new contact
'''
form = ContactForm()
if form.validate_on_submit():
return render_template('web/new_contact.html',form = form)
f = form.filename.data
f.save(os.path.join("./static/upload/", f.filename))
return redirect(url_for('new_contact'))
print(f)
my_contact = Contact()
form.populate_obj(my_contact)
db.session.add(my_contact)
try:
db.session.commit()
# User info
flash('Contact created correctly', 'success')
return redirect(url_for('contacts'))
except:
db.session.rollback()
flash('Error generating contact.', 'danger')
return render_template('web/new_contact.html', form=form)
With the limited info you gave, I will try to implement the functionality you want.
Your ContactForm can stay like that:
class ContactForm(FlaskForm):
name = IntegerField('File Name', validators=[Length(min=-1, max=100, message='You cannot have more than 100 characters')])
filename = FileField()
Then you pass the form object to template, from custom flask route, lets call it for the purpose of explaining, contact route:
#app.route('/contact')
def contact():
contact_form = ContactForm()
return render_template('contact.html'
contact_form = contact_form)
And in your template, that I called in this example contact.html, you render your form:
<form action="" method="post" enctype="multipart/form-data">
{{ contact_form.csrf_token }}
{{ contact_form.name }}
{{ contact_form.filename}}
<input type="submit"/>
</form>
In this form, we want with action="" to POST data on the same route, that is, contact route. So in this example we should also validate data in contact() method of flask app. But what is enctype="multipart/form-data" you may be wondering ?
First result searching for what it is gave us results:
The enctype attribute specifies how the form-data should be encoded when submitting it to the server.
Note: The enctype attribute can be used only if method="post".
And for multipart/form-data:
No characters are encoded. This value is required when you are using forms that have a file upload control.
Lastly, we update the flask app contact route like so:
#app.route('/contact')
def contact():
contact_form = ContactForm()
if form.validate_on_submit():
f = contact_form.filename.data
name = contact_form.name.data
f.save(os.path.join("./static/contacts/", name))
redirect(url_for('contact'))
return render_template('contact.html'
contact_form = contact_form)
We've successfully collected data from a form, and saved file in contacts folder in static, with the name from a form. Maybe additionally we could use secure_filename from werkzeug.utils.

Flask-WTF: variable choices for SelectField in FieldList of FormField

I have a setup with a FieldList(FormField(SubForm)) where SubForm includes a SelectField whose choices are determined at runtime (in the view function). Currently I have the following setup, which basically derives a new class for SubForm with the choices each time it is called:
from flask import Flask, render_template_string, url_for
from wtforms import SelectField, StringField, FieldList, FormField
from flask_wtf import FlaskForm as Form
app = Flask(__name__)
app.secret_key = 'secret_key'
class BaseForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
class SubForm(BaseForm):
title = StringField('SubForm')
class RootForm(BaseForm):
entries = FieldList(FormField(SubForm))
#app.route('/test/', methods=['GET', 'POST'])
def form_viewer():
form = RootForm(title='Title')
subformclass = SubForm
subformclass = subformclass.append_field('options',
SelectField('Options', choices=[('%i' %i,'option%i' %i) for i in range(1,5)]))
if form.validate_on_submit():
while form.entries:
subform = form.entries.pop_entry()
print(subform.options.data)
else:
print(form.errors)
for entry in range(1,3):
subform = subformclass()
subform.title = 'SubTitle%i' %entry
form.entries.append_entry(subform)
html_template = '''<html>
<form action="{{ url_for('form_viewer') }}" method=post>
{{ form.hidden_tag() }}
{{ form.entries() }}
<input type=submit value=Submit>
</form>
</html>'''
return render_template_string(html_template, form=form)
My problem is now, that if the app restarts (because things happen) between a GET and POST request, then it raises AttributeError: 'UnboundField' object has no attribute 'data' while trying to access the submitted form data.
How can this be fixed? Or is there maybe another, better way to have a variable number of SelectFields with variable choices?
edit:
I think the problem was, that after FieldList was created, I changed SubForm. So now I am creating FieldList in a similar way
like SelectField:
formclass = RootForm.append_field('entries', FieldList(FormField(subformclass)))
But I am still wondering whether this is the way I am supposed to do it? It feels like, there might be a more elegant solution.

Flask putting form into URL

I've been working on a form that sends data to a scraper and simultaneously generates a URL from form input. The returned templates works flawlessly, but the URL change ends up giving me the entire form in the URL and I can't figure out why.
The URL ends up looking like this:
http://localhost/options/%3Cinput%20id%3D%22symbol%22%20name%3D%22symbol%22%20type%3D%22text%22%20value%3D%22%22%3E
I'd like it to look like this:
http://localhost/options/ABC
Form class:
class OptionsForm(Form):
symbol = StringField('Enter a ticker symbol:', validators=[Required(), Length(min=1, max=5)])
submit = SubmitField('Get Options Quotes')
Views:
# Where the form data ends up
#app.route('/options/<symbol>', methods=['GET', 'POST'])
def options(symbol):
# Created this try/except so I could test functionality - for example, I can do 'localhost/options/ABC' and it works
try:
symbol = request.form['symbol']
except:
pass
return render_template('options.html', symbol=symbol, company_data=OS.pull_data(symbol, name=True))
# Where the form lives
#app.route('/', methods=['GET', 'POST'])
def index():
form = OptionsForm()
print(form.errors)
if form.validate_on_submit():
return redirect(url_for('options', symbol=form.symbol.data))
return render_template('index.html', options_form=form)
Template:
<div id="options_div">
<form method="POST" name="symbol_form" action="{{ url_for('options', symbol=options_form.symbol) }}">
{{ options_form.hidden_tag() }}
{{ options_form.symbol(size=10) }}
{{ options_form.submit(size=10) }}
</form>
Any help would be appreciated.
Try adding enctype='multipart/form-data' to the form tag. It looks like your form is using application/x-www-form-urlencoded, the default.
Edit OK so check this out. When your template is being rendered there is no value in that data attribute (In the url_for call). When not referencing the data attribute (as your original question shows), you're referencing the actual form element (which is why you see all of that html being passed in the url). Here are your options (that I see):
Use some kind of frontend javascript to bind the form's action attribute to the value in the input box. Something like angular would help for this (but is overkill if you don't use any of its other features).
Just have the form POST to /options (no symbol in url). Then, grab the symbol attribute from the form data.

pass data from view to form as choices

Do not understand how to pass data obtained from form.data in view as choices to form field. I would really appreciate any help.
So, here is what I am doing: I choose data using checkboxes at page, and then I need to display chosen data (their names) at the same page with additional textfield near each. Later all textfields must be filled and 'names' and inserted data must be sent to server.
As I understand for this I need to render data with textfield using form. I suppose it could be this one:
forms.py
import methods
class MultiTextField(SelectMultipleField):
widget = widgets.TableWidget()
option_widget = widgets.TextInput()
class SelectedForm(Form):
choices = methods.selected()
value = MultiTextField('value',choices = choices)
views.py
from forms import ...
selected_data = {}
def return_selected():
return selected_data
methods.py
from views import return_selected
def selected():
data = return_selected()
choices = []
for key, value in data.iteritems():
for item in value:
choices.append((item, key))
return choices
Variable selected_data stores data that were chosen through other form.
If I run this code I got error ImportError: cannot import name return_selected. Probably it is because I import views in methods, methods in forms and forms in views o.O
I see no other way how to make what I need, but it does not work as it is.
Well, I found how to pass choices.
forms.py
class MultiTextField(SelectMultipleField):
widget = widgets.TableWidget()
option_widget = widgets.TextInput()
class SelectedForm(Form):
name = MultiTextField()
views.py
#app.route('/home', methods=['GET', 'POST'])
def show_work():
selected = SelectedForm(request.form)
choices = []
for key, value in selected_data.iteritems():
choices.append((key, value))
selected.name.choices = choices
return render_template('home.html', selected=selected)
It is possible to add form choices in views with selected.name.choices = choices.
But this form puts data in strange way:
home.html
<form name="selected" action="{{url_for('selected_work', url='result') }}" method="post">
<p> {{selected.name}}</p>
<input type="submit" value="Calculate">
</form>
in choices was lets say: [(1,"Apple"), (2,"Apple")] but html show label 'Apple' and near it textfield with inserted number 1 and again label 'Apple' with number 2 inserted in textfield.
When I submit form ofcourse it sends inserted data in textfield, which is ['1', '2']. But somehow I need obtain: [(Apple, 1 and value inserted in textfield),(Apple, 2, inserted value)].
You have a circular dependency. Views import forms, which imports methods, which imports forms.
But your proposed solution is extremely dangerous. You are storing data at module level, which is absolutely not thread-safe and will lead to information leaking between users. Do not do this. Store the data in the session, which is per-user.
Thank you Daniel for your advice about session. I store chosen data in session now.
Here is how I solved my task. I decided not to use wtforms form for rendering data chosen with checkboxes. I keep all choices in session
views.py
import methods
from forms import ...
def make_record(works):
if session['data']:
if not works:
pass
else:
for work in works:
session['data'].append(work)
else:
session['data'] = [works]
#app.route('/home', methods=['GET', 'POST'])
def show_work():
demount = DemountForm(request.form)
tile = TileForm(request.form)
chosen = []
for each in session['data']:
chosen.append(each)
selected = methods.get_selected(chosen)
return render_template('home.html', demount=demount, tile=tile, selected = selected)
#app.route('/home/<path:url>', methods=['GET', 'POST'])
def select_work(url):
db = url
if db:
form = DemountForm(request.form)
work = form.name.data
make_record(work)
return redirect(url_for('show_work'))
methods.py
def get_selected(ids):
selected = {}
for each in ids:
data = db_session.query(Work.name, Work.dimension).filter(Work.id==each).all()
data = data[0]
selected[each] = data
return selected
home.html
{% if selected %}
<div id="chosen">
<form action="/home">
{% for key, value in selected.iteritems() %}
{{value[0]}}<input type="text" id="{{key}}">{{value[1]}}<br>
{% endfor %}
<input type="submit" value="Submit">
</form>
</div>
{% endif %}
In such way I obtained what I needed. On one page I got menu with checkboxes and div which display choices and textfields near each to insert additional data.

Determining if a simple_form object has been submitted yet

I am building a Form object using pyramid_simpleform. I would like to have one method that builds the form object and once submitted it attempts to validate it.
If the input valid, I'll redirect the user to a confirmation page. Otherwise, I'll render an error page. I am using form.validate() to see if the input data fits the schema.
If the form has not been submitted, I'd like to display the form using a render_to_response. How do I detect whether the form has been submitted?
Quick answer
Like Furbeenator answered:
if request.method == "POST":
Long answer
You should split your logic in two different view functions.
Also, you should return a form renderer to be used by your template instead of rendering the form straight in the view function. For example:
./forms.py:
from formencode import Schema, validators
class RegistrationForm(Schema):
email = validators.Email(not_empty=True, strip=True)
password = validators.UnicodeString(not_empty=True)
password_confirm = validators.UnicodeString()
chained_validators = [validators.FieldsMatch(
'password', 'password_confirm',
messages=dict(invalidNoMatch=u'Password does not match'))]
./views/accounts.py:
from pyramid_simpleform import Form
from pyramid_simpleform.renderers import FormRenderer
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from pyramid.url import route_url
from ..forms import RegistrationForm
from ..models import DBSession, User
#view_config(route_name='accounts_register', renderer='/accounts/register.mako',
request_method='GET')
def register(request):
"""Shows the empty form."""
form = Form(request, schema=RegistrationForm)
return {'renderer': FormRenderer(form)}
#view_config(route_name='accounts_register', renderer='/accounts/register.mako',
request_method='POST')
def register_post(request):
"""Receives the posted form."""
form = Form(request, schema=RegistrationForm)
if form.validate():
user = User(form.data['email'], form.data['password'])
DBSession.add(user)
return HTTPFound(route_url('profile', request))
return {'renderer': FormRenderer(form)}
./templates/accounts/register.mako:
<form action="" method="post">
<div class="${' error' if renderer.is_error('email') else ''}">
<label for="email">Email</label>
${renderer.text('email', type='email', placeholder='Email')}
% if renderer.is_error('email'):
<span class="errors">${', '.join(renderer.errors_for('email'))}</span>
% endif
</div>
<!-- ... --->
</form>
If you have the request object, you can test it's method for "POST".
if request.method == "POST":

Categories