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')
Related
I am writing an app that has 3 users: Super Admin, Admin, and Students. the Super Admin and Admin have the same functionalities except for some pages that are restricted for the admin, they have the same login credentials. (name and password ).
The student uses matricule number and password to log in.
Here is how I set the login_manager.login_view:
login = LoginManager()
login.login_views = 'admin.sperAdminLogin'
login.login_views = 'students.studentLogin'
However when I log in as a student it works, but when I open up a new window and access the super admin login page I am directly redirected to his home page without entering the credentials.
How can I set the login_view to handle this?
I am using flask blueprint I have an admin blueprint for both super admin and admin and a Student blueprint.
Here is my code :
The main init.py
from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import MetaData
from flask_migrate import Migrate
from flask_login import LoginManager
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
login.login_view = 'admin.sperAdminLogin'
login.login_view = 'students.studentLogin'
login.login_message = "Veuillez vous connecter pour accéder à cette page."
login.login_message_category = 'info'
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
migrate.init_app(app, db, render_as_batch=True)
login.init_app(app)
# Registering blueprints
from sgp.errors import bp as errors_bp
app.register_blueprint(errors_bp)
from sgp.admin import bp as admin_bp
app.register_blueprint(admin_bp, url_prefix='/admin')
from sgp.students import bp as students_bp
app.register_blueprint(students_bp, url_prefix='/students')
from sgp.main import bp as bp
app.register_blueprint(bp)
return app
# Avoiding the circular import
from sgp import models
The model.py
from hashlib import md5
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
from flask import session
from flask_login import UserMixin
from sqlalchemy import ForeignKey
from sgp import db, login
class User(UserMixin, db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20),index=True, nullable=False)
first_name = db.Column(db.String(20), index=True, nullable=False)
last_name = db.Column(db.String(20), index=True, nullable=False)
email = db.Column(db.String(120), unique=True, index=True, nullable=False)
phone_number = db.Column(db.String(10), unique=True)
profile_pic = db.Column(db.String(20), nullable=False, default="default.jpg")
password_hash = db.Column(db.String(128), nullable=False)
status = db.Column(db.Boolean(), default=False)
type = db.Column(db.String(50))
__mapper_args__ = {
'polymorphic_identity':'user',
'polymorphic_on':type,
}
def set_password(self, password):
"""Take in self keyword and password and return the passwordhash"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Take in self and password and returns True or false"""
return check_password_hash(self.password_hash, password)
class SuperAdmin(User):
__tablename__= 'superadmin'
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
bordereaux = db.relationship("Bordereau", backref="superadmin", lazy="dynamic")
Payment_plans = db.relationship("PaymentPlan", backref="superadmin")
Payments = db.relationship("Payment", backref="superadmin", lazy="dynamic")
__mapper_args__ = {
'polymorphic_identity':'superadmin'
}
def __repr__(self):
"""
Takes in the keyword self and returns the instance of the super admin class
And it herited atributes
"""
return (
'Super-Admin, First-name %s, Last-name %s, Email %s'
'Gender %s, Login-time %s'
% (
self.first_name,
self.last_name,
self.email,
self.gender,
self.login_at,
)
)
class Student(User):
id : primary key & foreignkey
the id for the student user
gender : enum
the gender of the user
login_at : datetime
the login date and time the user was loged in to the system
date_of_birth : date
the date of birth of the user
creation_date : datetime
the date and time the use was created
student_matricule : int
the maricule of the student user
student_faculty : str
the faculty of the student
student_promotion : str
the promotion of the student user
"""
__tablename__= 'student'
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
gender = db.Column(db.Enum("H", "F", "Au", name="varchar"))
login_at = db.Column(db.DateTime())
date_of_birth = db.Column(db.Date())
creation_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
matricule = db.Column(db.Integer, unique=True, nullable=False)
faculty = db.Column(db.String(20))
promotion = db.Column(db.String(20))
bordereaux = db.relationship("Bordereau", backref="student", lazy="dynamic")
__mapper_args__ = {
'polymorphic_identity':'student',
}
def avatar(self, size):
digest = md5(self.email.lower().encode('utf-8')).hexdigest()
return 'https://www.gravatar.com/avatar/{}?d=identicon&s={}'.format(
digest, size)
class AccademicYear(db.Model):
___tablename__ = 'accademic_year'
id = db.Column(db.Integer, primary_key=True)
starting_date = db.Column(db.Date(), nullable=False)
ending_date = db.Column(db.Date(), nullable=False)
bordereaux = db.relationship("Bordereau", backref="accademic_year", lazy="dynamic")
Payment_plans = db.relationship("PaymentPlan", backref="accademic_year", lazy="dynamic")
Payments = db.relationship("Payment", backref="accademic_year", lazy="dynamic")
class Bordereau(db.Model):
__tablename__ = 'bordereau'
id = db.Column(db.Integer, primary_key=True)
bordereau_code = db.Column(db.String(6), nullable=True)
bordereau_img = db.Column(db.String(20), nullable=False, default="bordereau.jpg")
payment_motif = db.Column(db.String(20), nullable=False)
timestamp = db.Column(db.DateTime())
Sending_time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
super_admin_id = db.Column(db.Integer, db.ForeignKey("superadmin.id"))
student_id = db.Column(db.Integer, db.ForeignKey("student.id"))
accademic_year_id = db.Column(db.Integer, db.ForeignKey("accademic_year.id"))
class PaymentPlan(db.Model):
__tablename__ = 'paymentplan'
id = db.Column(db.Integer, primary_key=True)
payment_name = db.Column(db.String(20), unique=True, nullable=False)
payment_amount = db.Column(db.Numeric(), nullable=False)
payment_schadule = db.Column(db.Date(), nullable=False)
timestamp = db.Column(db.DateTime)
super_admin_id = db.Column(db.Integer, db.ForeignKey("superadmin.id"))
accademic_year_id = db.Column(db.Integer, db.ForeignKey("accademic_year.id"))
class Payment(db.Model):
__tablename__ = 'payment'
id = db.Column(db.Integer, primary_key=True)
matricule = db.Column(db.Integer, nullable=False)
amount = db.Column(db.Numeric(), nullable=False)
payment_motif = db.Column(db.String(20), nullable=False)
payment_type = db.Column(db.String(20), nullable=False)
carancy = db.Column(db.String())
register_time = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
timestamp = db.Column(db.DateTime)
super_admin_id = db.Column(db.Integer, db.ForeignKey("superadmin.id"))
accademic_year_id = db.Column(db.Integer, ForeignKey("accademic_year.id"))
#login.user_loader
def user_loader(id):
return User.query.get(id)
3.The super admin and student login form
class sperAdminLoginForm(FlaskForm):
"""A class to represent super admin login form"""
userName = StringField('Username',
validators=[DataRequired()])
# email = StringField('Email ou Username',
# validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
# Secure cookie for keeping the user stay loged in for while
remember_me = BooleanField('Remember Me')
submit = SubmitField('Se connecter')
class studentLoginForm(FlaskForm):
"""A class to represent student login form"""
matricule = StringField('Matricule',
validators=[DataRequired()])
password = PasswordField('password', validators=[DataRequired()])
# Secure cookie for keeping the user stay loged in for while
remember = BooleanField('Remember Me')
submit = SubmitField('Se connecter')
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()
I am using Flask as a Rest API for my WebApp.
In the frontend i use the User object quite often, which is why i need it from the backend to work with the user data.
My concern is, that the user object has an attribute password, which is obviously also sent to the frontend, when i make a request for a user object.
Should i define another class like UserPublic to send to the frontend and just strip out the password or is there a better way to do this with Flask, SQLAlchemy, Marshmallow?
I'm not sure if it's even a problem sending the password hash+salt to the frontend. I mean, i don't need it there, so why send it? Password check for login purposes is done in the backend anyway.
This is my User class:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True,autoincrement=True, nullable=False)
public_id = db.Column(db.String, nullable=False)
fname = db.Column(db.String, nullable=False)
lname= db.Column(db.String, nullable=False)
bday = db.Column(db.Date, nullable=False)
street = db.Column(db.String, nullable=False)
zip = db.Column(db.String, nullable=False) #Zip used
city = db.Column(db.String, nullable=False)
country = db.Column(db.String, nullable=False, default='Germany')
password = db.Column(db.String, nullable=False)
admin = db.Column(db.Boolean, default=False)
email = db.Column(db.String, nullable=False)
iban = db.Column(db.String)
bic = db.Column(db.String)
gender = db.Column(db.CHAR, default='m', nullable=False)
created_by = db.Column(db.String)
updated_by = db.Column(db.String)
membership_status_id = db.Column(db.Integer, db.ForeignKey('membership_status.membership_status_id'))
member_since = db.Column(db.Date)
bookings = db.relationship('Booking', backref="User", lazy='select')
Marshmallow Schema:
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
include_fk = True
This is the endpoint to get a user object:
#app.route('/users/<public_id>', methods=['GET'])
#jwt_required
def get_user(public_id):
logger.info('Getting user with id: '+str(public_id))
current_user = User.query.filter_by(public_id=get_jwt_identity()).first()
if not current_user.admin:
return jsonify({'message' : 'Not privileged for this action'})
user = User.query.filter_by(public_id=public_id).first()
if not user:
return jsonify({'message' : 'No user found with id '+str(public_id)})
user_schema = UserSchema()
return user_schema.jsonify(user), 200
See doc about overriding generated fields.
Here's how to exclude the field from the auto-generated schema:
class UserSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = User
include_fk = True
exclude = ("password", )
# You may want to only exclude id on dump but keep it on load
# In this case, add it here by calling `auto_field` yourself
password = ma.auto_field(load_only=True)
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.
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.