Flask extensions requiring models fields to have a particular convention - python

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.

Related

Flask Admin edit able user change their data

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.

sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers

This error happened when I tried to get access to the page. I didn't get errors when I created the tables, but seems like there are problems still.
The models are like this:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
sell_items = db.relationship('Item', backref='user')
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
item_name = db.Column(db.String(64), index=True)
item_image = db.Column(db.String(200), index=True)
price = db.Column(db.Float(10), index=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref='sell_items')
The whole error message is this
Triggering mapper: 'Mapper|User|user'. Original exception was: Error creating backref 'user' on relationship 'User.sell_items': property of that name exists on mapper 'Mapper|Item|item'
How can I fix this? What I want to do is to refer to username who sells the item, but I cannot. There is a problem with the relationships between the models.
When you use backref the backwards relationship is automatically created, so it should only be used in one side of the relationship. In your case, you can remove the sell_items in the User model and the User model will automatically get a relationship from Item.
To declare the relationshiop on both sides (in case you want to customize its name, for example, use back_populates='name_of_relationship_on_other_model'.
in your Item class, replace this line
user = db.relationship('User', backref='sell_items')
with this line
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
it should work that way, from there you can query like this item = Item.query.first(), then item.sell_items... to get the user who posted the item.
i hope it helps.

SQLAlchemy InvalidRequestError: failed to locate name happens only on gunicorn

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.
¯\_(ツ)_/¯

Why do I need to supply any arguments to Flask-SQLAlchemy's Column constructor?

In the basic examples of Flask-SQLAlchemy usage to define data models, type and other attributes of each column are specified; but much of this seems redundant and appears in fact to be ignored.
For example I seem to be able to replace
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
with nothing but the specification of the primary_key
class User(db.Model):
id = db.Column(primary_key=True)
username = db.Column()
email = db.Column()
or even values that don't correspond to the underlying database table
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Integer, unique=False)
email = db.Column(db.Integer, unique=False)
and still get a model that corresponds to the database (as specified in the first example).
Are any of the arguments to Column necessary for an existing database? If not, what purpose do they serve (especially since they seem to be ignored)?

SQLAlchemy: Dynamically loaded backreference to another module

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'))

Categories

Resources