FLASK-WTF update a unique column PYTHON/FLASK/SQLITE - python

The def clean_clean email isn't working. This function should do two things as shown by two ifs. The first checks if no changes have been made to the field and if no changes have been made validation doesn't start, this currently work. The second if should check if the email entered already exists and if so present a validation error message. As the second if doesn't if an exisitng email is entered this leads to SQL IntegrityError.
Model.py - table
class User(db.Model, TimestampMixin, UserMixin):
__tablename__ = 'user'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
email = db.Column(db.String(80), nullable=False, unique=True)
password_hash = db.Column(db.String(128))
first_name = db.Column(db.String(20), nullable=False)
last_name = db.Column(db.String(20), nullable=False)
forms.py
class EditUserForm(FlaskForm):
first_name = StringField('First Name', validators=[DataRequired()])
last_name = StringField('Last Name', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField(
'Repeat Password', validators=[DataRequired(), EqualTo('password')])
update = SubmitField('Update')
cancel = SubmitField('Cancel')
def clean_email(self):
cd = self.cleaned_data
email = cd(email=email.data)
# if email is not modified, so don't start validation flow
if self.instance.pk is not None and self.instance.email == email:
return cd
# check email is unique or not
if User.objects.filter(email=email).exists():
raise ValidationError("Email address already exists!".format(email))
return cd
SQL error when a existing email is entered
sqlite3.IntegrityError: UNIQUE constraint failed: user.email

In reference to this error listed:
sqlite3.IntegrityError: UNIQUE constraint failed: user.email
This means that you're trying to save a new entry into your 'user' table with an email that already exist. You must have the constraint in your models.py file that says something like
email = db.Column(db.String(120), unique=True, nullable=False)
If you want users to be able to save an email that's already being used by a different user, then you need to change this to...
email = db.Column(db.String(120), unique=False, nullable=False)
It's not clear to me what you're trying to do, but if you want to do a query to find if the email is in use by someone besides the current user, you could do something like this:
email_already_in_use_boolean = bool(db.session.query(User).filter(User.email==email.data).filter(User.id!=current_user_id).first())

Related

Flask-sqlalchemy table user has no column named email_address

I am learning Flask and I am trying to add a user to my database and I get this error.
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) table user has no column named email_address
[SQL: INSERT INTO user (username, email_address, password_hash, budget) VALUES (?, ?, ?, ?)]
class User(db.Model):
id = db.Column(db.Integer(), primary_key=True)
username = db.Column(db.String(length=30), nullable=False, unique=True)
email_address = db.Column(db.String(length=50), nullable=False, unique=True)
password_hash = db.Column(db.String(length=60), nullable=False)
budget = db.Column(db.Integer(), nullable=False, default=1000)
items = db.relationship("Item", backref="owned_user", lazy=True)
This is my model.
class RegisterForm(FlaskForm):
username = StringField(label="Username:")
email_address = StringField(label="Email:")
password1 = PasswordField(label="Password:")
password2 = PasswordField(label="Confirm Pasword:")
submit = SubmitField(label="Create Account")
This is the form.
user_to_create = User(username=form.username.data,
email_address=form.email_address.data,
password_hash=form.password1.data)
db.session.add(user_to_create)
db.session.commit()
This is the part I create the user.
I guess I changed the name of the column at some point after I created the table. After deleting and recreating the table it fixed.

Handling Duplicate Users In Flask and Flask SQLAlchemy

I want to allow anonymous users to post their comments in a blog without requiring them to login or sign up. One anonymous user can post several comments using the same credentials during subsequent visits. However, I get SQLAlchemy IntegrityError during a second attempt.
I have a simple form, a simple model and a basic route.
comment.py
class CommentForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
comment = TextAreaField('Comment', validators=[DataRequired()])
submit = SubmitField('Post')
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(500))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
route.py
#app.route('/personal_blog', methods = ['GET', 'POST'])
def personal_blog():
form = CommentForm()
if form.validate_on_submit():
user = User(username = form.username.data, email = form.email.data)
post = Post(body = form.comment.data, author = user)
db.session.add(user)
db.session.add(post)
db.session.commit()
flash('Your comment is now live!')
return redirect(url_for('personal_blog', _anchor='translate-hover'))
posts = Post.query.order_by(Post.timestamp.desc()).all()
return render_template('personal_blog.html', form = form, posts = posts)
I have tried several suggested solutions and the closet I have come to was to add validation to the form, such as this:
class CommentForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
comment = TextAreaField('Comment', validators=[DataRequired()])
submit = SubmitField('Post')
def __init__(self, original_username, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
self.original_username = original_username
def validate_username(self, username):
if username.data != self.original_username:
user = User.query.filter_by(username=self.username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
This, however, needs the addition of #app.route('/personal_blog/<username>') which essentially does not work for my case. I want to have this anonymous user post subsequent comments without the need of changing either username or email.
Personally, I have tried to add validation within the route (if user.username is not None: flash('Use different credentials!')) to notify the user that the email or username they are trying to use has already been used, and it works, but this is not what I want the app to do. The user can simply use the very same credentials as before. How can this be achieved?
The solution to this is to remove the unique constraint from the User table.
Instead of:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True) <----------here
email = db.Column(db.String(120), index=True, unique=True) <----------here
posts = db.relationship('Post', backref='author', lazy='dynamic')
I have this:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True) <----------here
email = db.Column(db.String(120), index=True) <----------here
posts = db.relationship('Post', backref='author', lazy='dynamic')
As soon as I update the database, the changes take effect and I am able to allow a single anonymous user to post more than one comment using the same credentials.

Flask Security updating password not working

I am trying to use flask-Security to change the password. Basically, there are no errors being thrown but the hash in my database is not updating. When creating the initial password, everything works fine.
However, when I try to update using the code in update.py the password isn't updating. I appreciate your insight. Thank you.
update.py
buyer = Buyers.query.filter_by(id=buyer_id).first()
buyer.password = generate_password_hash(form.password.data)
db.session.commit()
flash('Password has been updated')
models.py
class Buyers(db.Model, UserMixin):
__tablename__ = 'buyers'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True, nullable=False)
firstname = db.Column(db.String(64), index=True, nullable=False)
lastname = db.Column(db.String(64), index=True, nullable=False)
company = db.Column(db.Integer, db.ForeignKey('company.id'))
password_hash = db.Column(db.String(128))
def __init__(
self, email, firstname, lastname, company, password
):
self.email = email
self.firstname = firstname
self.lastname = lastname
self.company = company
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
Not totally sure what you are trying to accomplish - but the code snippets have your User model with a field password_hash - not password. Flask-Security requires certain defined model names to work.
Not sure where you are getting check_password_hash() and generate_password_hash()....
The way Flask-Security updates password is:
user.password = hash_password(password)
_datastore.put(user)
_datastore.commit()
Oh - and I assume you are Flask-Security-Too (which is a maintained fork).....

StringField in mongodb requirements

I am wondering if I can add a requirment to the setup of my User document to check for a specific string. The idea is when a User Document is created with an email address, I want to make sure the email is from a college so it should end in ".edu"
example:
"john.doe#college.edu" is acceptable but "john.doe#gmail.com" is not
Here is my code:
class User(db.Document, UserMixin):
name = db.StringField(max_length=255, unique=True)
email = db.StringField(max_length=255, unique=True)
phone = db.StringField(max_length=255, unique=True)
password = db.StringField(max_length=255)
active = db.BooleanField(default=True)
confirmed_at = db.DateTimeField()
roles = db.ListField(db.ReferenceField(Role), default=[])
There is an optional regex argument you can define:
email = db.StringField(max_length=255, unique=True, regex=r'.*?boston\.edu$')
And, also, why not use a specific EmailField in this case:
email = db.EmailField(max_length=255, unique=True, regex=r'.*?boston\.edu$')

Populate WTForm MultiCheckboxField with Flask-Principal Roles

I'm working on an edit user page for my flask app, and I can't seem to figure out how to render the user's current roles like I do other variables, such as email.
Here's my models, form, and view:
#Flask-Principal Role Model
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
def __repr__(self):
return '<Role %r>' % (self.name)
#SQLALchemy User Model
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
first_name = db.Column(db.String(128))
last_name = db.Column(db.String(128))
business_name = db.Column(db.String(128))
active = db.Column(db.Boolean())
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
#WTForms User Form
class UserForm(Form):
first_name = StringField('first name', validators= [Required()])
last_name = StringField('last name', validators= [Required()])
business_name = StringField('business name', validators= [Required()])
email = StringField('email', validators = [Required(), Email()])
active = BooleanField('active')
roles = MultiCheckboxField('roles', coerce=int)
#Edit User View
#app.route('/admin/users/<id>/edit/', methods = ['GET', 'POST'])
#roles_required('admin')
def edit_user(id):
user = User.query.filter_by(id = id).first()
editform = UserForm()
# This is how I've assigned choices for other MultiCheckboxField forms, but I haven't
# needed to populate the MultiCheckboxField from a user model before with role objects.
editform.roles.choices = [(x.id,x.name) for x in Role.query.all()]
if editform.validate_on_submit():
pass
editform.first_name.data = user.first_name
editform.last_name.data = user.last_name
editform.business_name.data = user.business_name
editform.email.data = user.email
editform.active.data = user.active
#The below doesn't show all the available roles, just the current roles assigned.
editform.roles.data = user.roles
return render_template("user_edit.html",
title = "Edit User",
user = user,
editform = editform)
So then, does anyone know how to have WTForms display all the Roles available, and have checked the ones which are currently in the user.roles list?
data for a multi-select field is a list of form_data after it has been coerced by your coerce callable - so what you provide needs to match what's available as the first entry in each tuple you provide to choices - in this case, an integer. That said, if you change your code to:
editform.roles.data = [role.id for role in user.roles]
you should see all the appropriate checkboxes checked.

Categories

Resources