So I created a basic Flask-User app with multiple roles and datastructure like this:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
active = db.Column('is_active', db.Boolean(), nullable=False, server_default='0')
first_name = db.Column(db.String(50), nullable=False, default='')
last_name = db.Column(db.String(50), nullable=False, default='')
email = db.Column(db.String(255), nullable=False, unique=True)
confirmed_at = db.Column(db.DateTime())
# Relationships
user_auth = db.relationship('UserAuth', uselist=False)
roles = db.relationship('Role', secondary='user_roles',
backref=db.backref('users', lazy='dynamic'))
def is_active(self):
return self.active
class UserAuth(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
# User authentication information
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, default='')
# Relationships
user = db.relationship('User', uselist=False)
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
class Role(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
class UserRoles(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'))
I try to show it in Flask-Admin:
class AdminView(ModelView):
def is_accessible(self):
access = current_user.is_authenticated and current_user.has_role('admin')
return access
admin = Admin(app, name='My Admin', template_mode='bootstrap3')
admin.add_view(AdminView(User, db.session))
admin.add_view(AdminView(UserAuth, db.session))
admin.add_view(AdminView(Role, db.session))
admin.add_view(AdminView(UserRoles, db.session))
Why It shows in such a strange manner:
And how to see and be able to edit user roles when editing a user?
You have not defined a suitable string representation for instances of your UserAuth class. A suitable example would be:
class UserAuth(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
# User authentication information
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, default='')
# Relationships
user = db.relationship('User', uselist=False)
def hash_password(self, password):
self.password_hash = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password_hash)
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
return self.username
This needs to be done for all your models.
Related
Here is the relevant code.
I'm writing a Flask application where users can join pools and compete against others within those pools (currently, pools and users are in a many to many relationship). Each user will need a rating for each pool that he/she is in (Elo Rating), and I'm not sure how to implement that into my existing structure. Any suggestions to implement it that either fit into my solution or change it would be appreciated.
(Right now, each user has a rating but it is shared amongst pools, which does not make sense for Elo)
def load_user(user_id):
return User.query.get(int(user_id))
pool_user = db.Table('pool_user',
db.Column('pool_id', db.Integer, db.ForeignKey('pool.id')),
db.Column('user_id', db.Integer, db.ForeignKey('user.id')))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
rating = db.Column(db.Integer, nullable=False)
pools = db.relationship('Pool', secondary=pool_user, backref=('members'))
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'])
return s.dumps({'user_id': self.id}).decode('utf-8')
#staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return None
return User.query.get(user_id)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}', '{self.rating}')"
class Pool(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), nullable=False)
def __repr__(self):
return f"Pool: {self.name}"
For me, you should replace association table (pool_user) with association model (UserPool).
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
pools = db.relationship('UserPool')
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'])
return s.dumps({'user_id': self.id}).decode('utf-8')
#staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return None
return User.query.get(user_id)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}'"
class Pool(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20), nullable=False)
members = db.relationship('UserPool')
def __repr__(self):
return f"Pool: {self.name}"
class UserPool(db.Model):
pool_id = db.Column(db.Integer, db.ForeignKey('pool.id'), primary_key=True),
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
rating = db.Column(db.Integer, nullable=False)
I have defined two model with relation between them as below:
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), nullable=False, unique=False)
db.relationship('User', backref='role', lazy='dynamic')
def __init__(self, name):
self.name = name
def __repr__(self):
return f'<Role id={self.id} name={self.name}>'
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
password = db.Column(db.String(40), unique=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __init__(self, username, password, role_id):
self.username = username
self.password = password
self.role_id = role_id
def __repr__(self):
return f'<User id={self.id} username={self.username} password={self.password}>'
Then inside shell I created entries as below:
> admin_role = Role('Admin')
> db.session.add(admin_role)
> db.session.commit()
> admin_user = User('adminusername', 'adminpassword',admin_role.id)
> db.session.add(admin_user)
> db.session.commit()
When I try to query model I get perfect result:
>>> admin_role = Role.query.first()
>>> admin_user = User.query.first()
>>> print(admin_role)
<Role id=1 name=Admin>
>>> print(admin_user)
<User id=1 username=adminusername password=adminpassword>
But when I try to access relation
print(admin_role.users)
print(admin_user.role)
I get errors Role object has no attribute users and User object has no attribute role respectively.
Typo? You have to assign db.relationship() instance to a variable.
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), nullable=False, unique=False)
- db.relationship('User', backref='role', lazy='dynamic')
+ users = db.relationship('User', backref='role', lazy='dynamic')
I am designing a database where user is super class and Customer and Admin are inheriting User class. So according to the documentation in SQL SQLAlchemy I define like this
User Class
class User(Base):
__tablename__ = 'user'
username = Column(String(40), primary_key=True)
is_verified = Column(Boolean, default=True)
type = Column(String(20), nullable=True)
password = Column(String(40))
first_name = Column(String(50), nullable=True)
last_name = Column(String(50), nullable=True)
image_url = Column(String(250), nullable=True)
email = Column(String(100), nullable=True)
phone = Column(String(15), nullable=True)
addresses = relationship("Address", back_populates="user")
customer = relationship("Customer", uselist=False, back_populates="user")
admin = relationship("Admin", uselist=False, back_populates="user")
def hash_password(self, password):
self.password = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password)
Customer Class
class Customer(Base):
__tablename__ = 'customer'
id = Column(Integer, primary_key=True)
username = Column(String(40), ForeignKey('user.username'))
user = relationship("User", back_populates="customer")
products = relationship("Review")
cart = relationship("Cart", uselist=False, back_populates="customer")
Admin Class
class Admin(Base):
__tablename__ = 'admin'
id = Column(Integer, primary_key=True)
username = Column(String(20), ForeignKey('user.username'))
user = relationship("User", back_populates="admin")
The problem is User and Customer are one to one , User and Admin are one to one . but I defied admin and customer in the user class.
Further when I delete user it's not deleting the customer automatically.
What is the proper way to implement this scenario ?
use cascade="save-update, merge, delete"
The delete cascade indicates that when a “parent” object is marked for deletion, its related “child” objects should also be marked for deletion. If for example, we have a relationship User.addresses with delete cascade configured
class User(Base):
__tablename__ = 'user'
username = Column(String(40), primary_key=True)
is_verified = Column(Boolean, default=True)
type = Column(String(20), nullable=True)
password = Column(String(40))
first_name = Column(String(50), nullable=True)
last_name = Column(String(50), nullable=True)
image_url = Column(String(250), nullable=True)
email = Column(String(100), nullable=True)
phone = Column(String(15), nullable=True)
addresses = relationship("Address", back_populates="user")
customer = relationship("Customer", uselist=False, back_populates="user",cascade="save-update, merge, delete")
admin = relationship("Admin", uselist=False, back_populates="user",cascade="save-update, merge, delete")
def hash_password(self, password):
self.password = pwd_context.encrypt(password)
def verify_password(self, password):
return pwd_context.verify(password, self.password)
I have the following badge (achievement) system database structure:
class Base(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(),
onupdate=db.func.current_timestamp())
class User(UserMixin, Base):
__tablename__ = 'users'
username = db.Column(db.String(20), nullable=False, unique=True)
email = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(200), nullable=False)
name = db.Column(db.String(30), nullable=False)
badges = db.relationship('UserBadge', backref='ubadge',
lazy='dynamic')
class Badge(Base):
__tablename__ = 'badges'
name = db.Column(db.String(35), unique=True)
description = db.Column(db.String(300))
imgfile = db.Column(db.String(80))
badges = db.relationship('UserBadge', backref='badge',
lazy='dynamic')
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.name, self.description)
class UserBadge(Base):
__tablename__ = 'userbadges'
user_id = db.Column(db.Integer(), db.ForeignKey('users.id'))
badge_id = db.Column(db.Integer(), db.ForeignKey('badges.id'))
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.user_id, self.badge_id)
So i can return all the badges by a specific user, using:
ubadges = UserBadge.query.filter_by(user_id=user.id).all()
It returns:
[<Achievement: 1 - 1>]
But instead of 1 (user_id) and 1 (badge_id) i want to show the users.name and badges.name. How can i access those attributes?
In your UserBadge class, just use:
def __repr__(self):
return '<Achievement: {} - {}>'.format(self.ubadge.name, self.badge.name)
It has both properties because you set them up using backref in the other classes.
PS: You might need to change the User backref to user, and then use self.user.name int he function above
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.Integer)
users = db.relationship('User', backref='role', lazy='dynamic')
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.Integer, unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
password_hash = db.Column(db.String(128))
confirmed = db.Column(db.Boolean, default=False)
def can(self, permissions):
print(self.role) #None
return self.role is not None and (self.role.permissions & permissions) == permissions
#main.route('/',methods=['GET', 'POST'])
def index():
form = PostForm()
print(current_user.role) #None
print(current_user.can(Permission.WRITE_ARTICLES)) #False
if current_user.can(Permission.WRITE_ARTICLES) and form.validate_on_submit():
post = Post(body=form.body.data, author=current_user._get_current_object())
db.session.add(post)
return redirect(url_for('.index'))
posts = Post.query.order_by(Post.timestamp.desc()).all()
return render_template('index.html', form=form, posts=posts)
sqlite> .tables
alembic_version posts roles users
sqlite> select * from roles;
1|Administrator|0|255
2|Moderator|0|15
3|User|1|7
there is data in roles, but print(current_user.role) is None,and When I call the can function in class User, print(self.role) is also None.
I can't understand why self.role is None?