Flask-Security: Customizing Registration - python

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') }}

Related

Custom validation for WTForm fileds in Flask

I have used the built-in validators, but none of them is printing a message on the page. Also, I want to create a custom validator to check duplicate username. I have written the function, as I am a beginner, I don't know how to use it. Pls resolve the problem.
from flask import Flask, app, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import InputRequired, EqualTo, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = "PQrs12t46uvvrty567"
class MyForm(FlaskForm):
username = StringField('Username', validators=[InputRequired(message="This field is required.")])
password=PasswordField('Password', validators=[InputRequired(message=("enter the password"))])
confirm_password=PasswordField('Confirm Password', validators=[EqualTo('password')])
submit = SubmitField('Register')
def isDuplicate():
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
#app.route('/')
def base():
form = MyForm()
return render_template('customvalidator.html', form = form)
#app.route('/submitform', methods=["GET","POST"])
def submitform():
form = MyForm()
if form.validate_on_submit():
return 'Form accepted successfully.'
else:
return 'Incorrect form data'
if __name__=="__main__":
app.run(debug=True)
HTML file
<!DOCTYPE html>
<html>
<head><title>My website</title></head>
<body>
<h1>Registration form</h1>
<form action="{{ url_for('submitform') }}" method="post">
{{ form.csrf_token }}
{{ form.username.label }}
{{ form.username }}
<ul>
{% for error in form.username.errors %}
<li style="color: red;">{{ error }} </li>
{% endfor %}
</ul>
<br>
{{ form.password.label }}
{{ form.password }} <br><br>
{{ form.confirm_password.label }}
{{ form.confirm_password }} <br><br>
{{ form.submit}}
</form>
</body>
</html>
Try changing the isDuplicate function to this:
def isDuplicate(form, field):
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username.data == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
Notice the added form and field parameters to the function to allow its use as a custom validator. form.username was also changed to form.username.data to access the data of the username field. (This whole function will need to be defined before the form.)
You will then need to add this custom validator to your list of validators for the username field in your form like so:
username = StringField('Username', validators=[InputRequired(message="This field is required."),isDuplicate])
With these changes, the custom validation should work, but we still haven't solved the issue of the custom error messages. To do this, add a novalidate attribute to your HTML form tag. Adding this will disable the default validation on forms and allow you to display custom messages.
With that in place, I believe the messages still won't work because of how you are handling the form submission. When the form does not validate, you will want to display the same form template instead of showing them whether it submitted or not.
My personal opinion is that merging your two form routes will be the simplest option. Have a route define the form, check for when it gets validated or not, and then render the form template. This will enable you to keep everything together and not render a different template when you only want to display the same template with a few added error messages.
Credits:
Custom validators (check the last point)
SO question on displaying validation messages
-which led me to this one on actually how to disable the default messages.

manually fill in flask form field

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.

How to set a jinja2 variable a value from another variable

In {% set form.username = user.get_username %} I want to make form.username equal to the user's username, so when the form is sent, I know who sent it. However, it raises the error: "Did you forget to register or load this tag?"
If I replace this line with {{forms.username}} it works, but it makes necessary for the user to fill the form himself, with whatever he wants, allowing him to submit a file with a username that is not his.
file.html (is included in the home.html):
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% set form.username = user.get_username %}
{{ form.file }}
<button type="submit">Upload</button>
views.py:
from django.shortcuts import render
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from data.models import UserData
from data.forms import UserDataForm
def model_form_upload(request):
if request.method == 'POST':
form = UserDataForm(request.POST, request.FILES)
if form.is_valid():
form.save()
else:
form = UserDataForm()
return render(request, 'data/ranking_index.html', {
'form': form
})
models.py:
from django.db import models
class UserData(models.Model):
username = models.CharField(max_length=40)
file = models.FileField(upload_to='userdata/')
uploaded_at = models.DateTimeField(auto_now_add=True)
forms.py:
from django import forms
from data.models import UserData
class UserDataForm(forms.ModelForm):
class Meta:
model = UserData
fields = ('username', 'file', )
You haven't included user when rendering the template. So as a result jinja2 fails to find the value for user. I suggest the following.
return render(request, '<your template>', {
'form': form
'user': user
})
You need to create this user object in the same function or import from somewhere else its created. Hope this helps!

Python Flask Save Form Data to DB

I am new to Flask and I am trying to save form data from a Flask form to database using SQLAlchemy and I am not having any luck. I have tried several methods from research I found both here and outside of this forum.
When I take the simplistic route, the web form works and I can enter data but it will not populate the DB.
----Models----
class QIDMapping(db.Model):
id = db.Column(db.Integer, primary_key=True)
qid_number = db.Column(db.Integer)
br_field_name = db.Column(db.String(75))
vendor_field = db.Column(db.String(75))
----Forms----
class QIDForm(Form):
qidnumber = IntegerField('qidnumber', validators=[DataRequired()])
brfieldname = StringField('brfieldname', validators=[DataRequired()])
vendorfieldname = StringField('vendorfieldname')
----Views----
from flask import render_template, flash, redirect, session, url_for,
request, g
from flask_wtf import form
from app import app, db
from .forms import QIDForm
from .models import User, QIDMapping
from flask.ext.sqlalchemy import SQLAlchemy
#app.route('/qidmapping', methods=['GET', 'POST'])
def qid_map_update():
form = QIDForm()
return render_template('qidmapping.html',
title='QID Mapping',
form=form)
----qidmapping.html----
{% block content %}
<h1>Map QIDs to Vendor File</h1>
<form action="" method="POST">
{{form.hidden_tag()}}
<p>
Please enter the QID, BrassRing Field Name and Vendor Tag
<br>
<h2>QID Number {{ form.qidnumber(size=25) }}<br></h2>
<h2>BR Field {{ form.brfieldname(size=25) }}<br></h2>
<h2>Vendor Field {{ form.vendorfieldname(size=25) }}<br></h2>
</p>
<p><br>
</p>
<p><input type="submit" value="Save Fields">
</p>
</form>
{% endblock %}
I have also tried the method in this post Flask - WTForm - save form to db
and when I do, I get a Method Not Allowed error and I'm not sure why.
----view for question 20837209 format----
#app.route('/qidmapping', methods=['GET', 'POST'])
def qid_map_update():
form = QIDForm()
if form.validate_on_submit():
newform = (
form.qidnumber.data,
form.brfieldname.data,
form.vendorfieldname.data
)
db.session.add(newform)
db.session.commit()
return redirect('/qidmapping')
return render_template('qidmapping.html',
title='QID Mapping',
form=form)
Any help would be greatly appreciated!
Try replacing
newform = (
form.qidnumber.data,
form.brfieldname.data,
form.vendorfieldname.data
)
db.session.add(newform)
with
m = QIDMapping()
m.qid_number = form.qidnumber.data
m.br_field_name = form.brfieldname.data
m.vendor_field = form.vendorfieldname.data
db.session.add(m)
... and if that doesn't work. Do your standard POST troubleshooting:
1) Verify POST request
2) Ensure CSRF is working correctly.
3) Log validation errors / success
4) Check for DB exceptions

Python Flask - How to use SubmitField to delete object?

I'm a bit confused as how to have a "Delete" button on a page that will delete the object currently in focus.
I'm trying to add this button to /edit/ to delete whichever id is open
Using Python3 and Flask
forms.py
class EditForm(Form):
name = StringField('Server Name', validators = [Length(1, 120), DataRequired()])
ip_address = StringField('IP Address', validators = [Length(1, 16), IPAddress()])
username = StringField('UCX User', validators = [Length(1, 64)])
password = StringField('UCX Password', validators = [Length(1, 64)])
description = StringField('Purpose/Description', validators = [Length(1-120)])
protocol = RadioField('Protocol', [DataRequired()],
choices=[('https', 'HTTPS'), ('http', 'HTTP')], default='https')
submit = SubmitField('Submit')
**delete = SubmitField('Delete')**
Routes.py
#servers.route('/edit/<id>', methods=['GET', 'POST'])
def edit(id):
server = UcxServer.query.filter_by(id=int(id)).first_or_404()
form = EditForm(obj=server)
if form.validate_on_submit():
form.to_model(server)
db.session.commit()
flash('Your changes have been saved.')
return render_template('addserver2.html', form=form)
Routes.py delete function:
#servers.route('/delete/<id>')
def delete(id):
server = UcxServer.query.filter_by(id=int(id)).first_or_404()
try:
db.session.delete(server)
db.session.commit()
flash('Successfully deleted the {} server'.format(server))
return redirect(url_for('servers.index'))
Template (addserver2.html):
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block page_content %}
<div class="page-header">
<h1>UCX Server</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
So basically, I can load the edit/ page, but how do I hook up the "Delete" SubmitField to call the /delete/?
Figured it out. Posting answer for future folks.
Not sure if best way, but only took 2 lines of code:
For the /edit/ route, I simply added this check.
if form.delete.data:
return redirect(url_for('servers.delete', id=id))
Which makes the entire edit route look like this:
def edit(id):
server = UcxServer.query.filter_by(id=int(id)).first_or_404()
form = EditForm(obj=server)
if form.delete.data:
return redirect(url_for('servers.delete', id=id))
if form.validate_on_submit():
form.to_model(server)
db.session.commit()
flash('Your changes have been saved.')
return render_template('addserver2.html', form=form)
Maybe you can use customized validators. Like this:
delete = SubmitField('Delete', validators=delete())
About how to make a function a customized validator, check this link. The custom validators section.

Categories

Resources