Trace Create and Change in Flask Models - python

I have developed an application in flask. I want to trace create and change. For admin I'm using flask-admin:
class MyModelAdmin(ModelView):
def on_model_change(self, form, model, is_created):
history = HistoryModel(
..
..
)
db.session.add(history)
db.session.commit()
I have History model for save changes:
class HistoryModel(db.Model):
__tablename__ = 'history'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
date = db.Column(db.DateTime)
user_id = db.Column(db.Integer, nullable=True)
object_type = db.Column(db.String(32), nullable=False)
I'm not sure that it is the right approach. BTW, how can I trace change and create for save in HistoryModel?

SQLAlchemy provide event listen for you. There is a decorate named listens_for in sqlalchemy.event. You use it to listens_for(HistoryModel, "after_insert") and listens_for(HistoryModel.date, "set"). Check the document about events and orm events.

The extension SQLAlchemy-continuum might be a worthwhile option to look into. It versions your model (data)

Related

How to ignore some models to migrate? [duplicate]

I am using Flask-SQLAlchemy to define my models, and then using Flask-Migrate to auto-generate migration scripts for deployment onto a PostgreSQL database. I have defined a number of SQL Views on the database that I use in my application like below.
However, Flask-Migrate now generates a migration file for the view as it thinks it's a table. How do I correctly get Flask-Migrate / Alembic to ignore the view during autogenerate?
SQL View name: vw_SampleView with two columns: id and rowcount.
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
info = dict(is_view=True)
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)
Which means I can now do queries like so:
ViewSampleView.query.all()
I tried following instructions on http://alembic.zzzcomputing.com/en/latest/cookbook.html and added the info = dict(is_view=True) portion to my model and the following bits to my env.py file, but don't know where to go from here.
def include_object(object, name, type_, reflected, compare_to):
"""
Exclude views from Alembic's consideration.
"""
return not object.info.get('is_view', False)
...
context.configure(url=url,include_object = include_object)
I think (though haven't tested) that you can mark your Table as a view with the __table_args__ attribute:
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
__table_args__ = {'info': dict(is_view=True)}
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)

Flask extensions requiring models fields to have a particular convention

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.

Design patterns for flask API implementation

I am using flask with flask-restplus and sqlalchemy.
My rest API function looks like the following:
#ns.route('/user')
class UsersCollection(Resource):
#jwt_optional
#permissions.user_has('admin_view')
#ns.marshal_list_with(user_details)
def get(self):
"""
Returns list of users.
"""
users = User.query.all()
return users
I am willing to add additional data to users collection which should be returned by the API. Some of this data may be stored in other DB tables, while other is stored in memory.
What I am usually doing is something like this:
#ns.route('/user')
class UsersCollection(Resource):
#jwt_optional
#permissions.user_has('admin_view')
#ns.marshal_list_with(user_details)
def get(self):
"""
Returns list of users.
"""
users = User.query.all()
# Add additional data
for user in users:
user.activity = UserActivity.query ... # from DB
user.online_status = "Active" # Taken somewhere from memory
return users
Obviously I don't like this approach of adding data to the User object on the fly. But what is the best design pattern to achieve it?
About the design pattern, I recommend a DAO and Service approach, here's a good and short article about it:
https://levelup.gitconnected.com/structuring-a-large-production-flask-application-7a0066a65447
About the models User/Activity relationship, I presume you have a mapped Activity model. If this is a One to One relationship, in the User model create a activityId field for the FK field and a activity relation where your ORM (I'll assume SQLAlchemy) will retrieve when user.activity is accessed.
class Activity(db.Model):
id = db.Column(db.Integer, primary_key=True)
descrption = db.Column(db.String(255))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
activity = db.relationship(Activity)
activityId = db.Column(db.Integer, db.ForeignKey(Activity.id))
If you have a Many to Many relationship you'll have to create a third class called ActivityUser to map the relation
class ActivityUser(db.Model):
id = db.Column(db.Integer, primary_key=True)
activity = db.relationship(Activity, lazy=True)
user = db.relationship(User, lazy=True)
activityId = db.Column(db.ForeignKey(Activity))
userId = db.Column(db.ForeignKey(User))
and you can retrieve the user activities like this (again assuming you use SQLAlchemy):
ActivityUser.query.filter(ActivityUser.userId == user.id).all()

How to set hash of a column in another column in flask-admin?

I want to save hash of name to hash_name column Also I use Flask-Admin to manage my data.
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode, unique=True, nullable=False)
hash_name = db.Column(db.Unicode, unique=True)
admin.add_view(ModelView(User, db.session))
Also I set default with uuid package for hash_name but this page in result had a problem .my uuid never changed . I refreshed but not changed
If you only use flask-admin's SQLAlchemy ModelViews for editing, then it's possible to do following:
class UserView(sqla.ModelView):
# Hide `hash_name` in list and form views
column_exclude_list = ('hash_name',)
form_excluded_columns = ('hash_name',)
# Generate new hash on `name` change
def on_model_change(self, form, model, is_created):
if len(model.name):
model.hash_name = generate_hash_name(model.name)
Otherwise use #mehdy's event approach.
I think you can use sqlalchemy's even listeners to manipulate your object before committing it to the database:
from sqlalchemy import event
...
#event.listens_for(User, "before_commit")
def gen_default(mapper, connection, instance):
instance.hash_name = hash_function(instance.name)
so before each commit it will be invoked and updates the hash_name attribute with the proper hash on name

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