Populate WTForm MultiCheckboxField with Flask-Principal Roles - python

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.

Related

Creating parent and child at the same time. Flask SqlAlchemy

I have created the model below to store user and profile data separately in my database
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
password = db.Column(db.String(255))
profile = db.relationship('Profile', backref='Profile', uselist=False)
class Profile(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(25))
last_name = db.Column(db.String(25))
email = db.Column(db.String(25), unique=True)
phone_number = db.Column(db.String(25), unique=True)
post_code = db.Column(db.String(25))
house_number = db.Column(db.String(25))
user = db.Column(db.Integer, db.ForeignKey('user.id'))
I have attempted to populate Profile Model via this method, however, it does not work.
#routes.route('/register', methods = ['POST'])
def register():
if request.method == 'POST':
data = request.get_json()
new_user = User(username = data['username'], password = data['password'])
new_user.profile.first_name = data['first_name']
new_user.profile.last_name = data['last_name']
new_user.profile.email = data['email']
new_user.profile.phone_number = data['phone_number']
new_user.profile.post_code = data['post_code']
new_user.profile.house_number = data['house_number']
db.session.add(new_user)
db.session.commit()
return {'msg' : 'sucess'}
I get this error, would you please explain what I am doing wrong? I noticed that User.profile column is not present inside my database, however, I thought that was normal for a ForiegnKey?
File "C:\routes\register.py", line 11, in register
new_user.profile.first_name = data['first_name']
AttributeError: 'NoneType' object has no attribute 'first_name'
What is the solution ? I am assuming I need to create Profile() model separately but how do I simultaneously link that to an uncommitted User() model?
A few ways to do this, but you probably want to create an __init__(...) for user where you initialize a Profile object.
Something like this...
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
password = db.Column(db.String(255))
profile = db.relationship('Profile', backref='Profile', uselist=False)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.profile = Profile()

How to create a DB Model for Anonymous users?

I have created a web app using Flask where you can leave a note as text. Ig goes well you leave a text and it got saved in our database. But it only happens when you are an authorized user and can't be done if you are an unauthorized (anonymous user) and if you attempt as such this error message pops up: "'AnonymousUserMixin' object has no attribute 'id'".
Here the code goes:
#home.py
def home():
if request.method == 'POST':
note = request.form.get('note')
if len(note) < 1:
flash('Note is too short!', category='error')
else:
new_note = Note(data=note, user_id=current_user.id)
db.session.add(new_note)
db.session.commit()
flash('Note added!', category='success')
return render_template("home.html", user=current_user)
And the DB models:
#models.py
class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
data = db.Column(db.String(10000))
date = db.Column(db.DateTime(timezone=True), default=func.now())
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(150), unique=True)
password = db.Column(db.String(150))
first_name = db.Column(db.String(150))
confirmed = db.Column(db.Boolean, nullable=False, default=False)
notes = db.relationship('Note')
You are setting a user_id value when creating a Note:
new_note = Note(data=note, user_id=current_user.id)
Evidently anonymous users don't have an id attribute, so you must either set user_id=None when creating notes for anonymous users or create a default user to represent anonymous users and user that user's id when creating notes.

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.

SQLAlchemy not committing changes -- Flask

So what I basically want to do is have a user fill out a form and have a boolean value set to True which tells me their a user. I set it to True when I send the POST request to the database, but when I go look at the column it says "[null]". I don't understand what I'm doing wrong....
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(120))
last_name = db.Column(db.String(120))
email = db.Column(db.String(180), unique=True)
password = db.Column(db.String(255))
isUser = db.Column(db.Boolean())
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,backref=db.backref('users', lazy='dynamic'))
#app.route('/post_user', methods=['POST'])
def post_user():
user = User(request.form['first_name'], request.form['last_name'], request.form['email'], request.form['password'])
user.isUser = True
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))

How to set form element name?

I have a web application written in Flask that uses WTForms, SQLAlchemy and corresponding Flask extensions, Flask-SQLAlchemy, Flask-WTF and WTForms-Alchemy.
For the following model:
class User(db.Model, UserMixin):
"""
Represents a registered user.
#TODO Override setter for password so it is always encrypted.
#TODO Add last_login column
"""
__tablename__ = 'User'
def __init__(self, username=None, password=None, email=None):
self.username = username
self.email = email
self.password = encrypt(password)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, default=u'Anonymous Lifter')
username = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
I try to create a Form the following way:
class LoginForm(ModelForm):
""" Form used to login a user that does not use social login. """
class Meta:
model = User
only = [u'username', u'password']
field_args = {u'password': {'widget': PasswordInput()}}
remember_me = BooleanField(u'Remember me')
My problem is that when I display the form on my page both username and password appear lower-cased. How can I set their labels(?)/names(?) to upper-case?
Use the info property when defining your columns in your model. For example:
class User(db.Model, UserMixin):
"""
Represents a registered user.
#TODO Override setter for password so it is always encrypted.
#TODO Add last_login column
"""
__tablename__ = 'User'
def __init__(self, username=None, password=None, email=None):
self.username = username
self.email = email
self.password = encrypt(password)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, default=u'Anonymous Lifter', info={'label': 'Name'})
username = db.Column(db.String, nullable=False, info={'label': 'Username'})
password = db.Column(db.String, nullable=False, info={'label': 'Password'})
Or use the field_args when defining your form:
class LoginForm(ModelForm):
""" Form used to login a user that does not use social login. """
class Meta:
model = User
only = [u'username', u'password']
field_args = {
u'password': {
'widget': PasswordInput(),
'label': 'PASSWORD'
},
u'username': {'label': 'USERNAME'},
}
remember_me = BooleanField(u'Remember me')

Categories

Resources