I'm a beginner in coding and currently learning Python. I have been using Flask and WTForms recently to create a registration form. Besides just following tutorials to build the form, I'm also trying to understand it a little bit and see how it works (just generally). So my question is about the StringField.
I have created an html file called registr.html and it will be rendered. Inside of this html file, there is two lines of code that will create a field for the user to input their username, see below:
{{ form.username.label }}
{{ form.username }}
And in my Python application, I have created something below, say Section A:
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
And below, say Section B:
#app.route("/register", methods=['GET', 'POST'])
def register():
form = RegistrationForm()
return render_template('register.html', title='Register', form=form)
Here is my question. I understand form.username.label will put the label "Username" above the input field so people know this is where they input their username. And then I think what form.username does is actually creating a field so people can enter something in there. So my understanding is that username is an attribute that is equal to StringField('Username', validators=[DataRequired()]), which is an object of class StringField, and since form is equal to RegistrationForm(), So executing form.username in the html file is like executing Registration.StringField('Username', validators=[DataRequired()]). Am I correct, here? If so, how executing an object StringField('Username', validators=[DataRequired()]) can create an input field in the html? Can we execute an object? I think we can only execute a method in a class, correct? So there must be something else going on that I don't understand. Again I'm still a beginner and learning Python. So any help is be greatly appreciated!
there is a default render widget. You can overwrite the widget if you need custom HTML to be rendered (https://wtforms.readthedocs.io/en/stable/widgets.html). The specific the render widget used is here:
class Input(object):
"""
Render a basic ``<input>`` field.
This is used as the basis for most of the other input fields.
By default, the `_value()` method will be called upon the associated field
to provide the ``value=`` HTML attribute.
"""
html_params = staticmethod(html_params)
def __init__(self, input_type=None):
if input_type is not None:
self.input_type = input_type
def __call__(self, field, **kwargs):
kwargs.setdefault("id", field.id)
kwargs.setdefault("type", self.input_type)
if "value" not in kwargs:
kwargs["value"] = field._value()
if "required" not in kwargs and "required" in getattr(field, "flags", []):
kwargs["required"] = True
return Markup("<input %s>" % self.html_params(name=field.name, **kwargs))
Related
I'm relatively inexperienced with Python and Flask, and am stuck trying to pass a variable to a WTForms class.
Here's what I have:
views.py
#app.route('/teacher/tasks/new')
#login_required
def new_hw_task():
classes = Class.query.filter_by(userid=current_user.userid).all()
form = NewTaskForm(classes = classes)
return render_template('/teacher/new_hw_task.html', form=form)
forms.py
class NewTaskForm(FlaskForm):
classes = SelectMultipleField('Select classes to assign this homework to', choices = [("1", "Class 1"), ("2","Class 2")])
new_hw_task.html
<div class="form-group">
{{ form.classes.label }}
{{ form.classes(class_="form-control selectpicker", placeholder=form.classes.description, title="Select at least one class to assign", show_tick=true)}}
</div>
I want the classes variable (an instance of a Class class defined in models.py - yes, yes, I know how sloppy it is to have a class called 'Class') to be accessible in forms.py so that I can replace the choices in the SelectMultipleField with ones from classes. However, I can't find a way to pass it through (you can see that I've tried putting classes=classes into the parentheses after NewTaskForm).
Actually, my preferred way to do this would be to simply access current_user (the session-based object set by flask_login) from within forms.py, but I appear to be unable to do that either, even if I import current_user at the top of the file.
Is anybody able to explain to me where I'm going wrong here, please?
The WTForms documentation for SelectField explains how to pass variables into a form from a view. It's as simple as assigning a list of choices to form.field.choices. In turn, you remove the choices= keyword argument from the field constructor.
Adapted for your case, it would look like this.
#app.route('/teacher/tasks/new')
#login_required
def new_hw_task():
classes = Class.query.filter_by(userid=current_user.userid).all()
form = NewTaskForm()
form.classes.choices = classes
return render_template('/teacher/new_hw_task.html', form=form)
I want to pass a str to be used as the prompt for a form. I thought it would be simple but it is proving to be difficult.
Here is my code:
class PostForm(FlaskForm):
post = TextAreaField(Question, validators=[DataRequired()])
submit = SubmitField('Submit')`
And,
form = PostForm('my question')
the corresponding html
{{ wtf.quick_form(form) }}
So, I still don't have an answer to the question, but I did manage to come up with a solution.
class PostForm(FlaskForm):
post = TextAreaField(_l('This no longer matters'), validators=[DataRequired()])
submit = SubmitField(_l('Submit'))
And then in the routes
from wtforms.fields.core import Label
form = PostForm()
form.post.label = Label("post", 'Real question goes here')}
The explanation for this is that TextAreaField creates a label attribute on post that is accessible and changable, but it needs to be formated correctly as a Label object from wtforms.fields.core. (Simply reassigning it as a string did not work). The representation of this object is:
<label for="post">Real question goes here</label>
And it is of type
<class 'wtforms.fields.core.Label'>
Today I'm figured out about a similar problem as yours. I wanted to pass a variable to FlaskForm. For a small CV creation app, I want to give the user an opportunity to create more than 1 entry for his work experience and I wanted to do it with FieldList and FormField. And I needed to do it on one page so in one form.
My solution is pretty simple python implementation of factory pattern for forms:
class ConstructorForm(FlaskForm):
...
work_experience_form = FieldList(FormField(WorkExperienceForm), min_entries=1, max_entries=1)
skills_form = FieldList(FormField(SkillsForm), min_entries=1, max_entries=1)
...
And here is my function for building extending forms:
def constructor_form(work_experience_forms=1, skills_forms=1):
class _ConstructorForm(ConstructorForm):
pass
_ConstructorForm.work_experience_form = FieldList(
FormField(WorkExperienceForm), min_entries=work_experience_forms, max_entries=work_experience_forms
)
_ConstructorForm.skills_form = FieldList(
FormField(SkillsForm), min_entries=skills_forms, max_entries=skills_forms
)
return _ConstructorForm()
Try this:
def PostForm(question)
class F(Flaskform):
post = TextAreaField(question, validators=[DataRequired()])
submit = SubmitField('Submit')
return F()
I have a Flask-WTF form with with DataRequired validators and related error messages. However, the Chrome browser seems to ignore my error messages and just displays "Please fill out this field". How do I get Flask to override this and show my messages?
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired
class SignupForm(FlaskForm):
first_name = StringField('First Name', validators=[DataRequired(message='Hey, put in your first name!')])
last_name = StringField('Last Name', validators=[DataRequired("What, you can't remember your last name?")])
email = StringField('Email', validators=[DataRequired('Gonna need your email address!')])
password = PasswordField('Password', validators=[DataRequired('Really need a password, Dude!')])
submit = SubmitField('Sign Up')
As of WTForms 2.2, the HTML required attribute is rendered when the field has a validator that sets the "required" flag. This allows the client to perform some basic validations, saving a round-trip to the server.
You should leave it to the browser to handle this. The messages are standard and adjusted for the user's computer's locale. There is a JavaScript API to control these messages (see Stack Overflow and MDN), although WTForms doesn't provide any integration with it (yet, a good idea for an extension).
If you really want to disable this, you can pass required=False while rendering a field.
{{ form.name(required=False) }}
You can disable it for a whole form instead by overriding Meta.render_field.
class NoRequiredForm(Form):
class Meta:
def render_field(self, field, render_kw):
render_kw.setdefault('required', False)
return super().render_field(field, render_kw)
You can disable it for multiple forms by inheriting from a base form that disables it.
class UserForm(NoRequiredForm):
...
You can also disable client validation without changing as much code by setting the novalidate attribute on the HTML form tag.
<form novalidate>
</form>
See the discussion on the pull request adding this behavior.
in my code work this:
On html file:
{{ CustomForm(form.birth, class="form-control", required=False ) }}
On .py file:
birth = DateField('date of birth', validators=[DataRequired(message="My custom message")])
Referencing the following Form.
CONTROL_CHOICES = Session.query(schema.OfficeType).order_by(schema.OfficeType.descr).all()
CONTROL_CHOICES = [(office.id, office.descr) for office in CONTROL_CHOICES]
class ControlForm(Form):
institution = RadioField('Institution', choices=CONTROL_CHOICES)
date = DateField('Date')
submit = SubmitField('SUBMIT')
Simple HTML
<form action="composition_profile" method="get">
{{control_form.hidden_tag()}}
{{control_form.institution.label}}
{{control_form.institution}}
{{control_form.date.label}}
{{control_form.date}}
{{control_form.submit}}
</form>
The radio fields are successfully printed.
However, when filling out the form, validate_on_submit() prints no errors, but does not execute code within the if
validate() prints the following error.
{'institution': ['Not a valid choice']}
#app.route('/composition_profile', methods=['GET', 'POST'])
def composition_profiles():
if request.method == 'GET':
if request.args.get('institution') and request.args.get('date'):
form = ControlForm(request.args)
print(form.institution.data)
if form.validate():
print('terms')
print(form.errors)
Any idea how it is resulting in an improper choice? Im not really sure what's going on. Changing it to QuerySelectField works, but I'd like the radio functionality
Consider re-factoring:
def my_view():
class F(MyBaseForm):
pass
F.username = TextField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, TextField(name.title()))
form = F(request.POST, ...)
# do view stuff
Source: http://wtforms.simplecodes.com/docs/1.0.1/specific_problems.html#dynamic-form-composition
The Issue appears to have been caused due to the fact that WTForm expects the Value field, to be a String as opposed to an Integer
Changing
CONTROL_CHOICES = [(office.id, office.descr) for office in CONTROL_CHOICES]
To
CONTROL_CHOICES = [(str(office.id), office.descr) for office in CONTROL_CHOICES]
Alleviates the issue
I am trying to add a special template property to one of the form fields which I will than use to render the form in template tag. Here is the code:
form = ProfileForm()
for field in form:
if field.name == 'email':
field.template = 'email_field.html'
This way, original form variable is not modified. Is there a way to achive my goal?
I'm going to assume you might want to build a html5 email field:
from django import forms
from django.forms.widgets import Widget
from django.utils.safestring import mark_safe
class Html5Email(Widget):
def render(self, name, value, attrs=None):
return mark_safe(u'<input name="custom-email" type="email" />')
class YourForm(forms.Form):
html5_email = forms.CharField(widget=Html5Email())
I came up with the above by glancing at the Django source code. Since I haven't personally use the above in an actual project, the code will probably need to be fleshed out.