manually fill in flask form field - python

Someone wrote a page which lets you fill out a form and do something one at a time, I am working on a another page with a "bulk" functionality, which needs two fewer form fields.
I have been trying to recycle their Form object rather than define a new one which is only very slightly different.
Can explain this with the example from the docs:
https://flask.palletsprojects.com/en/1.0.x/patterns/wtforms/
The form object is created:
from wtforms import Form, BooleanField, StringField, PasswordField, validators
class RegistrationForm(Form):
username = StringField('Username', [validators.Length(min=4, max=25)])
email = StringField('Email Address', [validators.Length(min=6, max=35)])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
There is an endpoint:
#app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate():
user = User(form.username.data, form.email.data,
form.password.data)
db_session.add(user)
flash('Thanks for registering')
return redirect(url_for('login'))
return render_template('register.html', form=form)
and register.html (part of it below)
<form method=post>
<dl>
{{ render_field(form.username) }}
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm) }}
{{ render_field(form.accept_tos) }}
</dl>
<p><input type=submit value=Register>
</form>
Now say you are making another page which does not need the email field but needs all the other fields. If you delete the line:
{{ render_field(form.email) }}
out of the html, the page will render okay, but when the user clicks submit, form.validate() will be False and so it will not submit.
How can you give the form object something in the .py file so that it ignores a field which failed to be sent across from the browser?
Tried
form.email = "field not needed here"
...before the validation step, but it didn't seem to work

I guess that you want something like this:
The form object:
from wtforms import Form, BooleanField, StringField, PasswordField, validators
class CustomForm(Form):
username = StringField('Username', [validators.Length(min=4, max=25)])
password = PasswordField('New Password', [
validators.DataRequired(),
validators.EqualTo('confirm', message='Passwords must match')
])
confirm = PasswordField('Repeat Password')
accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
Your new endpoint, let's say we want to create a User without email:
#app.route('/endpoint', methods=['GET', 'POST'])
def register():
form = CustomForm(request.form)
if request.method == 'POST' and form.validate():
user = UserWithoutEmail(form.username.data, form.password.data)
db_session.add(user)
flash('Thanks for registering')
return redirect(url_for('login'))
return render_template('register.html', form=form)
and register.html to show everything but email:
<form method=post>
<dl>
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm) }}
{{ render_field(form.accept_tos) }}
</dl>
<p><input type=submit value=Register>
</form>

I have found a tidy solution adapted from here
class RegistrationForm2(RegistrationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
delattr(self, 'email')
I like it cause it does not involve repeating the whole class again. Not sure if it is more or less elegant than filling in fake values in the .py form and avoiding defining a new form but it works.

Related

Flask wtforms ValidationError not displayed to user

I'm using flask wtforms with input validation. Everything's working fine, except with validation failure my ValidationError message is not getting displayed to the user...
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField, ValidationError # URLField, EmailField
from wtforms.validators import DataRequired, Email, EqualTo
class SignupForm(FlaskForm):
name = StringField("Name:", validators=[DataRequired()])
email = StringField("Email:", validators=[DataRequired(), Email()])
password = PasswordField("Password:", validators=[DataRequired(), EqualTo('pass_confirm', message='Passwords must match')])
pass_confirm = PasswordField("Confirm:", validators=[DataRequired()])
submit = SubmitField("Create Account")
def validate_email(self, field):
if User.query.filter_by(email=field.data).first():
raise ValidationError('Your email has already been registered.')
def validate_name(self, field):
if User.query.filter_by(name=field.data).first():
raise ValidationError('This username is taken.')
and in my view:
#users.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
url = form.name.data # todo: generate unique url
user = User(name=form.name.data,
url=url,
email = form.email.data,
password = form.password.data)
db.session.add(user)
db.session.commit()
return redirect(url_for('users.login'))
return render_template('signup.html', form=form)
and this is the template:
<div class="jumbotron">
<h1>Sign up Page</h1>
<p>Please fill out the form</p>
<form method="POST">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.name.label(class='form-group') }}
{{ form.name(class='form-control') }}
</div>
<div class="form-group">
{{ form.email.label(class='form-group') }}
{{ form.email(class='form-control') }}
</div>
<div class="form-group">
{{ form.password.label(class='form-group') }}
{{ form.password(class='form-control') }}
</div>
<div class="form-group">
{{ form.pass_confirm.label(class='form-group') }}
{{ form.pass_confirm(class='form-control') }}
</div>
{{ form.submit(class='btn btn-primary') }}
</form>
</div>
Upon submitting an invalid form (either invalid email or name) the form simply returns blank, without indicating what the user did wrong. How can I display those validationerrors to the user? Much appreciated!
form must get some data
#users.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm(request.form)
or
#users.route('/signup', methods=['GET','POST'])
def signup():
form = SignupForm(request.json)
I believe you are importing ValidationError incorrectly
Try this importing it from wtforms.validators like this->
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField # URLField, EmailField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
In addition to this, you can add flash messages to your screen from your routes code such as this.
from flask import flash
if form.validate_on_submit():
flash(f'Info was saved to the database for user {form.name.data}', success)
else:
flash('Failed to save to database', 'danger')

Flask WTForm not validating, no form errors reported

I'm trying to setup a flask webapp with some basic login/register system controlled by flask-WTF forms.
Here is my code:
html
<!-- Register form -->
<div class="form">
<div class="form-area">
<h2>Register</h2>
<form action="{{ url_for('register') }}">
{{ form.csrf_token() }}
{{ form.hidden_tag() }}
{{ form.name(placeholder='name') }}
{{ form.surname(placeholder='surname') }}
{{ form.email(placeholder='email') }}
{{ form.password(placeholder='password') }}
{{ form.confirm_password(placeholder='confirm password') }}
<input type="submit" value="Register">
</form>
<p>Already registered? Log in here</p>
</div>
<div class="error-area">
{% for error in form.confirm_password.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
</div>
class
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import InputRequired, Length, EqualTo
class RegisterForm(FlaskForm):
name = StringField('name', validators=[InputRequired()])
surname = StringField('surname', validators=[InputRequired()])
email = StringField('email', validators=[InputRequired()])
password = PasswordField('password', validators=[InputRequired(), Length(min=6)])
confirm_password = PasswordField('confirm passord', validators=[InputRequired(), Length(min=6), EqualTo(password)])
flask
#app.route('/register')
def register():
#declare a register form
form = RegisterForm()
#validate form
if form.validate_on_submit():
print('validate')
return '<h1>Success</h1>'
else:
print('not validated')
print(form.errors)
return render_template('register.html', form=form)
The problem with my code is that validation seems not to be working. Even if I fill the form with the "valid" input, form.validate_on_submit() always fail.
What I can't understand is that even when I try to print array errors, no error shows.
What am I missing?
There are a few issues here. Firstly, in your html, you haven't set a method attribute for the form. This means that it defaults to GET, which is why the form isn't validating. This can be changed like so:
<form action="{{ url_for('register') }}" method='POST'>
Incidentally, as the view that loads the form is the same as the target, you can leave out the action attribute, giving us:
<form method='POST'>
Secondly, in your class, you have a couple of issues with the confirm_password field. Firstly, you have a typo in PasswordField('confirm passord'. Secondly, the EqualTo() validator expects a string, not a field. We need to change this line in full to:
confirm_password = PasswordField('confirm password', validators=[InputRequired(), Length(min=6), EqualTo('password')])
Finally, in your flask, we need to accept POST requests to the view. This can be done by altering #app.route():
#app.route('/register', methods=['POST', 'GET'])
Having made these changes, the form should work as expected.

How can i avoid a user from registering an already used email in django?

I was testing my registration view and i noticed that if i try to register using an alreaxy existing Username, i'll get an error; if i try to register using an already existing email, the app will let me do so.
Obviously, i don't want someone to register multiple accounts with the same email on my site. I'm fairly new to Django, and since i noticed that the form checked if an username already exists, i thought it would do the same with the email field.
I don't really know how to go from that, should i work on my view or on the form? And how can i make it loop through my DB and find if an e-mail had already been registered? I thought email = form.cleaned_data.get('email') would do the trick, but it didn't.
Any help is appreciated.
Here is my view:
def register(request):
if request.method == "POST":
form = NewUserForm(request.POST)
if form.is_valid():
user = form.save()
username = form.cleaned_data.get('username')
email = form.cleaned_data.get('email')
messages.success(request, f"New Account Created: {username}")
login(request, user)
messages.info(request, f"You are now logged in as {username}")
return redirect("main:homepage")
else:
for msg in form.error_messages:
messages.error(request, f"{msg}: {form.error_messages[msg]}")
form = NewUserForm
return render(request,
"main/register.html",
context={"form":form})
And here is the form:
class NewUserForm(UserCreationForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ("username", "email", "password1", "password2")
def save(self, commit=True):
user = super(NewUserForm, self).save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
According to your code, all you need to do is raise a ValidationError from your form
class NewUserForm(UserCreationForm):
email = forms.EmailField(required=True)
def clean_email(self):
if User.objects.filter(email=self.cleaned_data['email']).exists():
raise forms.ValidationError("the given email is already registered")
return self.cleaned_data['email']
class Meta:
model = User
fields = ("username", "email", "password1", "password2")
I can help - here is what I use for this very thing.
Starting with the form in forms.py.
You are already using a form that inherits from UserCreationForm (this is good).
The added method included with my form is just for styling with class of 'input'.
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = User
fields = ['first_name', 'email', 'username', 'password1', 'password2']
labels = {
'first_name': 'Name',
}
def __init__(self, *args, **kwargs):
super(CustomUserCreationForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs.update({'class': 'input'})
Then in the views.py, my code is similar but above the test for 'elif form.is_valid()', one needs to insert the email variable and the 'if' statement to test if it already exists.
If the email already exists then display the message.error.
If the email doesnt exist then move onto the 'elif' statement.
After the 'elif' the next two lines of code ensure any username is all lowercase before subsequently the user is saved via 'user.save()' and then success messages and redirects take place.
Here is the view:
def registerUser(request):
form = CustomUserCreationForm()
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
email = request.POST['email']
if User.objects.filter(email=email).exists():
messages.error(request, 'That email is already in use')
elif form.is_valid():
user = form.save(commit=False)
user.username = user.username.lower()
user.save()
messages.success(request, 'User account was created!')
login(request, user)
return redirect('edit-account')
else:
messages.error(request, 'An error has occured during registration')
context = {'page': page, 'form': form}
return render(request, 'users/login_register.html', context)
Now here is the Template that goes with the above form and view.
The outer 'for loop', loops through the fields in form and outputs (with the styling applied in forms.py) and the inner loop, loops through any error messages (provided by UserCreationForm) and outputs adjacent the field (these errors wont include the email duplicate warning message, that is handled via 'from django.contrib import messages').
Template:
<form method="POST" action="{% url 'register' %}" class="form auth__form">
{% csrf_token %}
{% for field in form %}
<div class="form__field">
<label for="formInput#text">{{field.label}}</label>
{{field}}
{% for error in field.errors %}
<p style="color: red;">{{error}}</p>
{% endfor %}
</div>
{% endfor %}
<div class="auth__actions">
<input class="btn btn--sub btn--lg" type="submit" value="Sign In" />
</div>
</form>
If you are using a main.html (that is extended by other templates), then place the following code above the '{% block content %}' tags in main.html. The following code will loop through any messages as they occur and output towards the top of your page.
The following will include the warning/error message when a user attempts to register with an email address already in use.
{% if messages %}
{% for message in messages %}
<div class="alert alert--{{message.tags}}">
<p class="alert__message">{{message}}</p>
<button class="alert__close">x</button>
</div>
{% endfor %}
{% endif %}
Be sure to have imported this at top of views.py:
from django.contrib import messages
And be sure to have imported this at top of forms.py:
from django.contrib.auth.forms import UserCreationForm

Django User Profile doesn't update

I am setting up a website in Django, where users can see their profiles and update details (like email, password, etc) or delete own account. But updating the form is not acknowledged at all by the user. I am using the standard built-in User model.
forms.py:
class UserDetailsForm(forms.ModelForm):
password = forms.CharField(widget = forms.PasswordInput())
class Meta:
model = User
fields = ('first_name','last_name','email','password','is_active')
views.py:
#login_required
def edit_profile(request):
user = User.objects.get(username=request.user)
form = UserDetailsForm(request.POST or None,
initial={'first_name':user.first_name,
'last_name':user.last_name,
'email':user.email,
'password':user.password,
'is_active':user.is_active,})
if request.method == 'POST':
if form.is_valid():
user.save()
messages.info(request, 'This is a debug message')
return HttpResponseRedirect(reverse('account'))
context = {"form": form}
return render(request, "iFood/user-account.html", context)
user-profile.html:
...
<form method="POST" action="{% url 'account' %}" class="" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<input type="submit" name="" value="Edit and Save">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
First of all you can't change a password like that. You should take the password that user entered and set it with user_obj.set_password():
Django docs: Change password
And for your form:
You doing it wrong with user.save(). There is nothing to save for user object. You should save the form using form.save().
Also request.user is the actual user object not the username.
forms.py:
class UserDetailsForm(forms.ModelForm):
password = forms.CharField(widget = forms.PasswordInput())
class Meta:
model = User
fields = ('first_name','last_name','email'','is_active')
views.py:
#login_required
def edit_profile(request):
user = request.user
form = UserDetailsForm(request.POST or None, instance=user)
if request.method == 'POST':
if form.is_valid():
# Save the changes but password
form.save()
# Change password
new_password = form.cleaned_data.get('password')
if new_password:
user.set_password(new_pass)
messages.info(request, 'This is a debug message')
return HttpResponseRedirect(reverse('account'))
context = {"form": form}
return render(request, "iFood/user-account.html", context)

Flask-Security: Customizing Registration

I'm trying to use Flask-Security to login and register users. However, I'm trying to do two things. The first being change the default /registration to /signup which I believe I've done correctly.
The second is I want to customize the registration form. I can get the page loaded but whenever I submit the form nothing happens. No data is sent to the backend or inserted into the sqlite database.
I'm also not sure if I need to write my own app.route function to handle creating a new user and/or checking if they already exist. I'd like to be able to use flask-security's register but just tweak it. If that's even possible.
Here's my main python file:
from flask import Flask, redirect, flash, session, request, render_template
from flask_sqlalchemy import SQLAlchemy
import os, time
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
app.config['DATABASE'] = 'data.db'
app.config['SECURITY_TRACKABLE'] = True
app.config['SECURITY_REGISTERABLE'] = True
app.config['SECURITY_REGISTER_URL'] = '/signup'
app.config['SECURITY_REGISTER_USER_TEMPLATE'] = 'security/register.html'
# enable this if you want to send email and confirm
# app.config['SECURITY_CONFIRMABLE'] = True
db = SQLAlchemy(app)
from user import Security, User, user_datastore
from forms import logform, signupform, RegisterForm
security = Security(app, user_datastore, register_form=signupform, login_form=logform,
confirm_register_form=signupform)
db.create_all()
db.session.commit()
#app.route('/')
def hello_world():
log = logform(csrf_enabled=False)
flash("HELLO.")
return render_template("index.html", form=log)
#app.route('/login')
def login():
print "/LOGIN REQUESTED"
form = logform(csrf_enabled=False)
return render_template("security/login_user.html", login_user_form=form, security=security)
#not sure if I need this function at all
#app.route('/signup', methods=['GET', 'POST'])
def signup():
print "/SIGNUP REQUESTED"
form = signupform(csrf_enabled=False)
# form = RegisterForm()
if request.method == 'GET':
return render_template("security/register.html", register_user_form=form, security=security)
else:
#log = logform(csrf_enabled=False)
if form.validate_on_submit() or form.validate():
print "HERE NOW SOMEONE SUBMITTED SOMETHING"
print form.email.data
print form.username.data
print form.password.data
user_datastore.create_user(form)
db.session.commit()
return redirect('/')
I'm not sure if I need the /signup function or if flask-security will handle it on default.
Next here's my logform and signupform classes:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo
from flask_security.forms import LoginForm, RegisterForm, Form
class logform(LoginForm):
login = StringField('login', validators=[DataRequired()])
# password = PasswordField('password', validators=[DataRequired()])
class signupform(RegisterForm):
# email = StringField('email', validators=[Email(), DataRequired()])
username = StringField('username', [DataRequired()])
# password = PasswordField('password', validators=[DataRequired()])
Here's the current registration form I'm trying to use. I'm not sure if I should use url_for('signup') as flask-security's default registration form uses url_for_security('register').
<!DOCTYPE html>
{% extends "base.html" %}
{% block content %}
<div id="signup">
<h1>Create Account.</h1>
<h3>TESTING</h3>
{# not sure what to put as my action. default flask-security uses url_for_security('register') #}
<form id="logfrm" name='register_form' method="POST" action="{{ url_for('signup') }}">
{# figure out how to make input required #}
{{ register_user_form.email(placeholder="email", type="email") }}<br>
{{ register_user_form.username(placeholder="username") }}<br>
{{ register_user_form.password(placeholder="password", type="password") }}<br>
{# register_user_form.confirm(placeholder="confirm password") #}<br>
{{ register_user_form.submit }}
<input type="submit" value="SIGNUP" >
</form>
</div>
{% endblock %}
Any help would be great, thanks!
Thanks for the help. That really cleared up most of the trouble I was having.
The only other thing wrong was in my form. I was forgetting that RegisterForm requires a password confirmation so I also added:
{{ register_user_form.password_confirm(...) }}
and to also include a app.config['SECRET_KEY'] = 'blah' in my main file so that the registration doesn't run into an error.
You don't need to define a new route for the registration.
In your signupform class that is inherited from RegisterForm you only need to override the validate method with your custom logic and return True or False as appropriate.
In your html template for registration the action url is {{ url_for_security('register') }}
The same applies for a custom login form - the url action though is {{ url_for_security('login') }}

Categories

Resources