I'm using Flask-Admin to manage my CRUD.
There are three roles in my app, which is superuser, operator and client.
In this app, operators must ask superuser to register their account, to change their data and others.
But for the client which is uncounted numbers, I want they can register their account or editable their account information by own.
For now, the client has can register by own, but now I want the client can editable their information individually without through superuser.
So far, I just can edit the account information by superuser, like this screenshot:
So for now, I want client can edit their name, email, password or other information by their own, but also separate their data with the other clients.
Here is the snippet of my model:
roles_users = db.Table(
'roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))
)
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
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))
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
class Operator(User):
__tablename__ = 'operator'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
class Client(User):
__tablename__ = 'client'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
So, how to do that with Flask-Admin..?
Flask-Security comes with a built-in form and view for password change. I would recommend using that. https://pythonhosted.org/Flask-Security/customizing.html
to edit user info via Flask-Admin view, you can override these methods by doing the following. Don't forget to add 'client' as accepted role in your flask-admin User class.
The custom filter has to filter on current_user_id, so no other user profile can be editted.
def get_query(self)
if "superuser" in current_user.roles:
return self.session.query(self.model) # as original source code
else: # for all other roles
return self.session.query(self.model).filter(
< insert custom filter here> )
def get_count_query(self):
if "superuser" in current_user.roles:
return self.session.query(func.count('*')).select_from(self.model) # as original source code
else: # for all other roles
return self.session.query(func.count('*')).filter(
<insert custom filter here> )
An alternative solution would be so build a custom view (without using flask-admin) and call it /myprofile.
Related
If someone were to have a flask web app with several "types" of user, for example in a school setting there may be "teacher", "student" and "admin". Each user needs to have a way to log in to the app, but Flask-Login only provides one #login.user_loader. My question is similar to this question, about multiple user classes, but the difference is that each type of user does not have the same model structure as the others. For example, if one were to use flask-sqlalchemy and declared the following models:
class Teacher(db.Model, UserMixin):
id = db.Column(db.Integer,primary_key=True)
title = db.Column(db.String(32))
pwd_hash = db.Column(db.String(128))
first_name = db.Column(db.String(128))
last_name = db.Column(db.String(128))
students = db.relationship('Student',backref='teacher',lazy='dynamic') #because a teacher will teach multiple students
class Student(db.Model, UserMixin):
id = db.Column(db.Integer,primary_key=True)
username = db.Column(db.String(64),unique=True)
teacher_id = db.Column(db.ForeignKey('teacher.id'),db.Integer) #for the ease of things, the student is only ever taught by one teacher
class Admin(db.Model, UserMixin):
email = db.Column(db.String(128),unique=True)
name = db.Column(db.String(128))
The user_loader is normally used like this, with the User class here being a generic user class:
#login.user_loader
def load_user(id):
return User.query.get(int(id))
This means that you have to have just one User class that can be logged in. The normal approach would be to condense the Student, Teacher and Admin class into one User class, with a role field in the model which tells you what level of access they are. But this brings up multiple problems. For example, not all students have an email at their disposal yet the administrative account for the school needs one. Similarly, the relationship between students and teachers would fail because there is no model for the teachers to have students in their class etc.
So how can I implement a way of there being multiple user models with different attributes and model fields that can each be logged in separately with flask_login?
Your User needs only an id and a relationship to the tables which that user corresponds to. You can create this by creating a table for roles and roles-relationships:
class User(db.Model, UserMixin):
""""""
__tablename__ = "user"
# Core
id = db.Column(db.Integer,primary_key=True)
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
class Role(db.Model, RoleMixin):
__tablename__ = "role"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
You can check for a given role using the following:
class User(db.Model, UserMixin):
# all the args go here...
...
def has_role(self, role_name):
"""Does this user have this permission?"""
my_role = Role.query.filter_by(name=role_name).first()
if my_role in self.roles:
return True
else:
return False
Next, create a decorator which checks for a role
from flask import redirect
from flask_login import current_user
from functools import wraps
def require_role(role):
"""make sure user has this role"""
def decorator(func):
#wraps(func)
def wrapped_function(*args, **kwargs):
if not current_user.has_role(role):
return redirect("/")
else:
return func(*args, **kwargs)
return wrapped_function
return decorator
Step 3: Apply the decorator in operations:
#app.route("/")
#login_required
#require_role(role="teacher")
def teachers():
"""Only allow teachers"""
# Otherwise, proceed
return render_template('teachers.html')
I am trying to make a discord like application, but I haven't been able to properly set the relationship. I am using a "members" association table to link the users to a server. I believe that the users and the servers should keep a copy of the relationship so that a user can print all the servers it is connected to and a server can see all the members it has. This is the code I have so far.
members = db.Table('members',
db.Column('member_id', db.Integer, db.ForeignKey('user.id')),
db.Column('workspace_id', db.Integer, db.ForeignKey('workspace.id')))
class User(db.Model):
__tablename__="user"
id=db.Column(db.Integer,primary_key=True)
username=db.Column(db.String,nullable=False, unique=True)
workspaces = db.relationship(
"Workspace",
secondary=members,
primaryjoin=(members.c.workspace_id == id),
backref=db.backref('members', lazy='dynamic'), lazy='dynamic')
class Workspace(db.Model):
__tablename__="workspace"
id=db.Column(db.Integer,primary_key=True)
workspaceName=db.Column(db.String,nullable=False)
subgroups=db.relationship("subGroup",backref="workspace",lazy=True)
code = db.Column(db.String, nullable=False)
users = db.relationship(
"User",
secondary=members,
primaryjoin=(members.c.member_id == id),
backref=db.backref('members', lazy='dynamic'), lazy='dynamic')
Do I append from both sides of the relationship or just append once?
So I'm working on an webapp using Flask. I followed a naming convention in my data models but it seemed that this convention does not properly integrate well with Flask-extensions for specific field naming, quoting for instance, from Flask-Security extension
Models
Flask-Security assumes you’ll be using libraries such as SQLAlchemy,
MongoEngine, Peewee or PonyORM to define a data model that includes a
User and Role model. The fields on your models must follow a
particular convention depending on the functionality your app
requires. Aside from this, you’re free to add any additional fields to
your model(s) if you want. At the bare minimum your User and Role
model should include the following fields:
User
id
email
password
active
...
Now assume my user model is something like:
class User(UserMixin, db.Model):
'''This model represents all types of Users registered'''
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
user_email = db.Column(db.String(64), unique=True, index=True)
user_password_hash = db.Column(db.String(128))
If I have to change my model's field to what Flask-extension requires, that requires me to change in a lot of files, which is a tedious task to do.
What I thought of is something like this:
class User(UserMixin, db.Model):
'''This model represents all types of Users registered'''
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
id = self.user_id #For Flask-Extensions
user_email = db.Column(db.String(64), unique=True, index=True)
email = self.user_email #For Flask-Extensions
user_password_hash = db.Column(db.String(128))
password = self.user_password_hash #For Flask-Extensions
How bad is this solution and what alternatives I have?
I think you can use Synonyms.
I didn't check but I think this should works.
from sqlalchemy.orm import synonym
class User(UserMixin, db.Model):
__tablename__ = 'users'
user_id = db.Column(db.Integer, primary_key=True)
id = synonym('user_id')
user_email = db.Column(db.String(64), unique=True, index=True)
email = synonym('user_email')
user_password_hash = db.Column(db.String(128))
password = synonym('user_password_hash')
that's more or less workable and something I've done. I'd recommend implementing it using a property:
#property
def id(self):
return self.user_id
If Flask-Security needs the property accessible at the class level as well, you can use SQLAlchemy's hybrid_property instead.
Okay, so I have the following. In user/models.py:
class User(UserMixin, SurrogatePK, Model):
__tablename__ = 'users'
id = Column(db.Integer, primary_key=True, index=True)
username = Column(db.String(80), unique=True, nullable=False)
email = Column(db.String(80), unique=False, nullable=False)
password = Column(db.String(128), nullable=True)
departments = relationship("Department",secondary="user_department_relationship_table", back_populates="users")
and in department/models.py:
user_department_relationship_table=db.Table('user_department_relationship_table',
db.Column('department_id', db.Integer,db.ForeignKey('departments.id'), nullable=False),
db.Column('user_id',db.Integer,db.ForeignKey('users.id'),nullable=False),
db.PrimaryKeyConstraint('department_id', 'user_id') )
class Department(SurrogatePK, Model):
__tablename__ = 'departments'
id = Column(db.Integer, primary_key=True, index=True)
name = Column(db.String(80), unique=True, nullable=False)
short_name = Column(db.String(80), unique=True, nullable=False)
users = relationship("User", secondary=user_department_relationship_table,back_populates="departments")
Using the flask development server locally this works totally fine. However, once I deploy to the standard python buildpack on heroku, the cpt/app.py loads both modules to register their blueprints:
from cpt import (
public, user, department
)
...
def register_blueprints(app):
app.register_blueprint(public.views.blueprint)
app.register_blueprint(user.views.blueprint)
app.register_blueprint(department.views.blueprint)
return None
and eventually errors out with the following:
sqlalchemy.exc.InvalidRequestError: When initializing mapper
Mapper|User|users, expression 'user_department_relationship_table'
failed to locate a name ("name 'user_department_relationship_table' is
not defined"). If this is a class name, consider adding this
relationship() to the class after
both dependent classes have been defined.
I'd like to know if there's a better way to organize these parts to avoid this error obviously, but I'm more curious why this organization works fine on the development server but blows up something fierce on gunicorn/heroku.
Well I can't explain the discrepancy between heroku and the dev server, but I got the error to go away by changing the Department mode from
users = relationship("Department",secondary="user_department_relationship_table", back_populates="users")
to
users = relationship("User", secondary=user_department_relationship_table, backref="departments")
which sets up the User model automatically which in turn means I can delete any mention of Department and the relationship table on that end.
¯\_(ツ)_/¯
Let's suppose that I have a User model in one module.
class User(Model):
id = Column(Integer, primary_key=True)
Then I want to add a dynamically-loaded, many-to-one relationship towards User from a Post model in another module. Also, I don't want to 'pollute' the User's model definition with relationships from this other module.
Is there a cleaner way of doing this other than adding a field to the User class from outside of the Post model, like this?
class Post(Model):
user_id = Column(Integer, ForeignKey('user.id'))
User.posts = relationship('Post', backref='user', lazy='dynamic')
Thanks
Well, you can define it in the Post model (see below)
class Post(Model):
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship('User', backref=backref('posts', lazy='dynamic'))