Flask-SQLAlchemy nullable=False - python

I'm messing around with Flask and the Flask-SQLAlchemy extension to create a simple registration form. In my User class, I have the attribute "email" set to nullable=False, but when I test the form on the site without including an email, it saves the new user to the db instead of throwing an exception as I expected. Any idea why that's happening? Thanks for looking!
Here's the code:
from flask import Flask, url_for, render_template, request, redirect
from flaskext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/kloubi.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(80), unique=True, nullable=False)
fname = db.Column(db.String(80))
lname = db.Column(db.String(80))
def __init__(self, username, email, fname, lname):
self.username = username
self.email = email
self.fname = fname
self.lname = lname
#app.route('/')
def index():
return render_template('index.html')
#app.route('/register/', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
new_user = User(fname = request.form['name'],
lname = request.form['surname'],
email = request.form['email'],
username = request.form['username'])
db.session.add(new_user)
db.session.commit()
return redirect(url_for('index'))
return render_template('register.html')
if __name__ == '__main__':
app.debug = True
app.run(host='0.0.0.0')

The problem is when you submit the webform without entering an email it will contain an empty string "" .. not None... and an empty string is not the same als null and it is ok to store it in the field.
I suggest using something like wtforms to validate the input of the user.

You can add a judgment if email != ''

Option1: If you are defining form by yourself in HTML
Just by using the required attribute in input HTML tag will prompt the user that an input field must be filled out before submitting the form.
<form action="#">
<input type="email" name="email" required>
<input type="submit">
</form>
Option2: If you are using WTforms to define the form
Then by using validators.required() while defining form class you can achieve the same result
class MyForm(Form):
email = EmailField(u'Email', [validators.required()])

you can use html5 attribute email directly in html. See this.

Related

flask sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users

I keep receiving this error on when trying to launch my application and, despite looking through many Stack overflow posts that had my same error, nothing I tried seems to work.
I have the database existing in my current directory, the databse_uri seems to be correct, I have configured the application before creating the database (db) and I have created the database before connecting to it. What am I doing wrong?
import api_requests #to process the requests from users
#creating the user class
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm #for creating forms through flask
from flask_wtf.file import FileField, FileRequired, FileAllowed
from wtforms import StringField, PasswordField, SubmitField, RadioField #for creating fields in input forms
from wtforms.validators import InputRequired, Length, ValidationError #for validating user input in the forms
from flask_login import UserMixin
app = Flask(__name__)
#app configurations
app.config["SECRET_KEY"]= SECRET_KEY
app.config["MAX_CONTENT_LENGTH"] = 100*1024*1024 #100MB max-limit per image
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] =False
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///Users.db'
bcrypt = Bcrypt(app)
db= SQLAlchemy(app)
login_manager=LoginManager()
login_manager.init_app(app)#will allow flask and login manager to work together when users are logging in
login_manager.login_view ="login"
class Users(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), nullable=False, unique=True)
password = db.Column(db.String(100), nullable=False)
#creating the registration form
class RegisterForm(FlaskForm):
username = StringField(validators=[InputRequired(), Length(min=4, max=100)], render_kw={"placeholder":"Username"})
password = PasswordField(validators=[InputRequired(), Length(min=4, max=100)], render_kw={"placeholder": "password"})
confirm_password = PasswordField(validators=[InputRequired(), Length(min=4, max=100)], render_kw={"placeholder": "confirm_password"})
submit = SubmitField("Register")
def validate_username(self, username):
existing_user_username = Users.query.filter_by(username=username.data).first()
if existing_user_username:
raise ValidationError("That username already exists. Please pick another one.")
#creating the login form
class LoginForm(FlaskForm):
username = StringField(validators=[InputRequired(), Length(min=4, max=100)], render_kw={"placeholder":"Username"})
password = PasswordField(validators=[InputRequired(), Length(min=4, max=100)], render_kw={"placeholder": "password"})
submit = SubmitField("Login")
#creating the upload image form
class UploadImage(FlaskForm):
file = FileField(validators=[FileRequired(), FileAllowed(['png', 'jpeg','jpg'], 'Images only!')]) #allow only files with the correct extension to be submitted
organs = RadioField('Label', choices=[('leaf','leaf'),('flower','flower'),('fruit','fruit'),('bark','bark/stem')])
upload = SubmitField("Upload")
#login_manager.user_loader
def load_user(user_id):
return Users.get(user_id) # loads the user object from the user id stored in the session
#app.route("/new_user", methods=["GET", "POST"])
def register_user():
form = RegisterForm()
if request.method == "POST":
if form.validate_on_submit():
if form.confirm_password.data != form.password.data:
flash("the two password fields don/t match, please enter them correctly")
return render_template('new_user.html', form = form)
hashed_password = bcrypt.generate_password_hash(form.password.data)
new_user = Users(username=form.username.data, password= hashed_password)
db.session.add(new_user)
db.session.commit()
return redirect(url_for("login"))
#insert something here
flash("Username already exists, please pick another one")
return render_template("new_user.html", form=form)
#app.route("/log", methods=["GET", "POST"])
def login():
form = LoginForm()
if form.validate_on_submit():
#check if user is in db
user = Users.query.filter_by(username =form.username.data).first()
if user:
if bcrypt.check_password_hash(user.password,form.password.data):
login_user(user)
return redirect(url_for("view_plants"))
flash("Username or password entered incorrectly. Please try entering them again.")
return render_template("index.html", form=form)
here is my code:
Check your Flask-SQLAlchemy version- you might need to downgrade to 2.5.1.

How does Flush error occur and how do I solve it?

I'm trying to make a login page I have been trying for weeks but this error keeps popping up:
sqlalchemy.orm.exc.FlushError: Instance <Users at 0x10bd8c580> has a NULL identity key.
The error lies in the register.py file. Apparently flask doesn't like me using .commit() or .add(). I've also tried to use .flush
but it gave me the same error still
register.py:
from flask import Blueprint, url_for, render_template, redirect, request
from flask_login import LoginManager
from werkzeug.security import generate_password_hash
import sqlalchemy
from models import db, Users
register = Blueprint('register', __name__, template_folder='../frontend')
login_manager = LoginManager()
login_manager.init_app(register)
#register.route('/register', methods=['GET', 'POST'])
def show():
if request.method == 'POST':
username = request.form['username']
email = request.form['email']
password = request.form['password']
confirm_password = request.form['confirm-password']
if username and email and password and confirm_password:
if password == confirm_password:
hashed_password = generate_password_hash(
password, method='sha256')
try:
new_user = Users(
username=username,
email=email,
password=hashed_password,
)
db.session.add(new_user)
db.session.commit()
except sqlalchemy.exc.IntegrityError:
return redirect(url_for('register.show') + '?error=user-or-email-exists')
return redirect(url_for('login.show') + '?success=account-created')
else:
return redirect(url_for('register.show') + '?error=missing-fields')
else:
return render_template('register.html')
Models.py:
from flask_login import UserMixin
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Users(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(15), unique=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String)
item = db.Column(db.String(25))
amount = db.Column(db.Integer)
I'm still a beginner with flask so excuse me if it's real obvious, any assistance would be very welcome!
You didn't specify what sql database you are using with sqlAlchemy. Anyways, the id is not being generated so the new_user has no identity. that's why your getting this error. To solve the problem modify your model as follows:
class Users(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(15), unique=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String)
item = db.Column(db.String(25))
amount = db.Column(db.Integer)
or you can use Sequence instead of autoincrement:
id = db.Column(db.Integer, primary_key=True, Sequence('user_seq'))
This is for the identity issue, but note that you need to specify user_loader for flask_login to work.

Why isn't Flask getting the proper datetime.now into sqlite? [duplicate]

This question already has answers here:
How Can I Automatically Populate SQLAlchemy Database Fields? (Flask-SQLAlchemy)
(2 answers)
Closed 4 years ago.
I am new to SQLAlchemy and have been unable to set the DateTime for created. I have tried using the "default" option found in many examples. I have also tried setting it manually (I have it commented out). Neither have worked so far. Any help would be appreciated.
models.py
import datetime
from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug import generate_password_hash, check_password_hash
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'users'
uid = db.Column(db.Integer, primary_key=True)
firstname = db.Column(db.String(40))
lastname = db.Column(db.String(40))
email = db.Column(db.String(120), unique=True)
created = db.Column(db.DateTime, default=datetime.datetime.utcnow())
confirmed = db.Column(db.DateTime, nullable=True)
pwdhash = db.Column(db.String(100))
def __init__(self, firstname, lastname, email, password):
self.firstname = firstname.title()
self.lastname = lastname.title()
self.email = email.lower()
self.set_password(password)
#self.created = datetime.datetime.utcnow()
self.confirmed = None
def set_password(self, password):
self.pwdhash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.pwdhash, password)
routes.py
from tasks import app
from datetime import datetime
from flask import render_template, request, flash, session, url_for, redirect
from forms import ContactForm, SignupForm, SigninForm
from flask.ext.mail import Message, Mail
from models import db, User, Tags, Tasks
#app.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignupForm()
if 'email' in session:
return redirect(url_for('profile'))
if request.method == 'POST':
if form.validate() == False:
return render_template('signup.html', form=form)
else:
newuser = User(form.firstname.data, form.lastname.data, form.email.data, form.password.data)
db.session.add(newuser)
db.session.commit()
session['email'] = newuser.email
return redirect(url_for('profile'))
elif request.method == 'GET':
return render_template('signup.html', form=form)
The problem with your default is that you're calling datetime.utcnow immediately there, and the value returned (at the time of class definition) is always used as default. You need to pass the callable itself like following:
# Note the lack of parenthesis after datetime.utcnow
created = db.Column(db.DateTime, default=datetime.datetime.utcnow)
This way SQLAlchemy will call datetime.utcnow itself upon row insert.

Testing a POST that uses Flask-WTF validate_on_submit

I am stumped on testing a POST to add a category to the database where I've used Flask_WTF for validation and CSRF protection. For the CRUD operations pm my website. I've used Flask, Flask_WTF and Flask-SQLAlchemy. It is my first independent project, and I find myself a little at a lost on how to test the Flask-WTForm validate_on_submit function.
Here's are the models:
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
name = db.Column(db.String(80), nullable=False)
email = db.Column(db.String(250), unique=True)
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True, unique=True)
name = db.Column(db.String(250), nullable=False, unique=True)
users_id = db.Column(db.Integer, db.ForeignKey('users.id'))
Here's the form:
class CategoryForm(Form):
name = StringField(
'Name', [validators.Length(min=4, max=250, message="name problem")])
And here's the controller:
#category.route('/category/add', methods=['GET', 'POST'])
#login_required
def addCategory():
""" Add a new category.
Returns: Redirect Home.
"""
# Initiate the form.
form = CategoryForm()
# On POST of a valid form, add the new category.
if form.validate_on_submit():
category = Category(
form.name.data, login_session['users_id'])
db.session.add(category)
db.session.commit()
flash('New Category %s Successfully Created' % category.name)
return redirect(url_for('category.showHome'))
else:
# Render the form to add the category.
return render_template('newCategory.html', form=form)
How do I write a test for the if statement with the validate_on_submit function?
You should have different configurations for your app, depending if you are local / in production / executing unit tests. One configuration you can set is
WTF_CSRF_ENABLED = False
See flask-wtforms documentation.
Using py.test and a conftest.py recommended by Delightful testing with pytest and SQLAlchemy, here's a test that confirms the added category.
def test_add_category_post(app, session):
"""Does add category post a new category?"""
TESTEMAIL = "test#test.org"
TESTUSER = "Joe Test"
user = Users.query.filter(Users.email==TESTEMAIL).first()
category = Category(name="Added Category", users_id=user.id)
form = CategoryForm(formdata=None, obj=category)
with app.test_client() as c:
with c.session_transaction() as sess:
sess['email'] = TESTEMAIL
sess['username'] = TESTUSER
sess['users_id'] = user.id
response = c.post(
'/category/add', data=form.data, follow_redirects=True)
assert response.status_code == 200
added_category = Category.query.filter(
Category.name=="Added Category").first()
assert added_category
session.delete(added_category)
session.commit()
Note that the new category is assigned to a variable and then used to create a form. The form's data is used in the post.
Working on the comments of #mas I got to this solution which worked for me:
topic_name = "test_topic"
response = fixt_client_logged_in.post('/create', data={"value":topic_name}, follow_redirects=True)
I am using this form class:
class SimpleSubmitForm(FlaskForm):
value = StringField(validators=[DataRequired()])
submit = SubmitField()
In this html file:
{{form.hidden_tag()}}
{{form.value.label("Topic", class="form-label")}}
{{form.value(value=topic_name, class="form-control")}}
<br/>
{{form.submit(value="submit", class="btn btn-primary")}}
Note that I am using the hidden_tag for the CSRF security, however when testing I have this extra line that de-activates it:
app.config['WTF_CSRF_ENABLED']=False
I have no idea how it actually works under the hood but my hypothesis is this: The wtform FlaskForm object looks at the "data" attribute of the request, which should be a dict. It then looks for keys in that dict that have the same name as its attributes. If it finds a key with the same name then it assigns that value to its attribute.

Error passing an html variable to a function in flask

I am trying to register a user into a sqlite database.
My form contains a hidden field with a numerical value.
When I process the form, it returns the following error:
"The browser (or proxy) sent a request that this server could not understand."
Which apparently means a variable is not passed to the function.
How could I address the problem please?
I have tried changing the data type to int() in the backend before sending to the function, I have made the field visible etcetc. Nothing worked.
Funny enough: when I use the "print" function in the backend and disable the "User.regUser(email, username, password, rank)", their values shows in the console... so I would assume the variables contain the info I need to pass to the function.
Here is the code:
Views:
#app.route('/register', methods=['GET', 'POST'])
def register():
#if request.method == 'POST':encrypt
email = request.form['email']
username = request.form['username']
password = generate_password_hash(request.form['pass1'])
rank = int(request.form['rank'])
print(email, password, username, rank)
User.regUser(email, username, password, rank)
# db.session.add(User(email, username, password, "50"))
# db.session.commit()
return render_template('register.html')
Models:
from pjctBB.views import db
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, nullable=False)
username = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
rank = db.Column(db.Integer, nullable=False)
def __init__(self, email, username, password, rank):
self.email = email
self.username = username
self.password = password
self.rank = rank
def regUser(self, email, username, password, rank):
db.session.add(User(email, username, password, rank))
db.session.commit()
Thanks a bunch!
From what I gather, you're trying to request from a form without first rendering it, meaning there is no form to get or post from. Try this:
#app.route('/register', methods=['GET', 'POST'])
def register():
if request.mthod == 'GET':
return render_template('register.html')
elif request.method == 'POST':
email = request.form['email']
username = request.form['username']
password = generate_password_hash(request.form['pass1'])
rank = int(request.form['rank'])
print(email, password, username, rank)
User.regUser(email, username, password, rank)
#db.session.add(User(email, username, password, "50"))
#db.session.commit()
return render_template('register.html')
That's assuming register.html exists.
Thank you all for your interest in my question. It got me thinking a bit :)
I asked my question slighly differently on SO and 2 users gave me a solution.
I ncase someone runs into a similar problem as mine, please look this thread for a possible solution:
Flask registration error: missing 1 positional argument

Categories