SQLAlchemy - Right way for models/methods in more files - python

In my project, based on Flask + SQLAlchemy I have more files for more models.
For example:
# file: models_users.py
user_roles_association = db.Table('user_roles_association',
Column('role_id',
db.Integer,
db.ForeignKey('roles.id')),
Column('user_id',
db.Integer,
db.ForeignKey('users.id'))
)
class User(UserMixin, Model):
__tablename__ = 'users'
id = Column(db.Integer, primary_key=True)
first_name = Column(db.String(40), nullable=True)
last_name = Column(db.String(40), nullable=True)
roles = db.relationship('Role',
secondary=user_roles_association,
backref=db.backref('users', lazy='dynamic'),
lazy='dynamic')
def __init__(self, first_name, last_name, **kwargs):
db.Model.__init__(self,
first_name=first_name,
last_name=last_name,
**kwargs)
def my_roles(user_id):
user = User.query.get(user_id)
return user.roles.order_by(Role.id).all()
Second file:
# file: models_cofig.py
class Role(UserMixin, Model):
__tablename__ = 'roles'
id = Column(db.Integer, primary_key=True)
name = Column(db.String(40), unique=True, nullable=False)
def __init__(self, name, **kwargs):
"""Create instance."""
db.Model.__init__(self,
name=name,
**kwargs)
def __repr__(self):
return str(self.name)
Okay, and question: What is a right way to make method, where I can use models from other model files?
Dirty way:
# file models_user.py
from apps.config import Role
class User(UserMixin, Model):
__tablename__ = 'users'
...
...
def my_roles(user_id):
user = User.query.get(user_id)
return user.roles.order_by(Role.id).all()
Note: Model Role is in second file.
And I need use right and clear way for custom methods across more models files.
Thanks for any answers.

Related

Alembic/Migrate does not recognize a materialized view

I have created a postgres materialized view following Jeff Windman's methodology from here:
How to make SQLAlchemy custom DDL be emitted after object inserted?
and here:
http://www.jeffwidman.com/blog/847/
The view factory and the model were adapted to the current project, but all functions and classes are virtual copies of the source model. The model was imported into a view and referenced in a template. Regrettably, it is completely ignored by Migrate(Alembic) and does not migrate/upgrade.
user.py
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
username = db.Column(db.String(100), unique=True, nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
individual_id = db.Column(UUID(as_uuid=True), db.ForeignKey('individuals.id', ondelete="CASCADE",
onupdate="CASCADE"), nullable=False)
role_id = db.Column(db.SmallInteger, db.ForeignKey('user_roles.id', ondelete='RESTRICT',
onupdate='CASCADE'), unique=False, index=True, nullable=False)
mv_user_individual = db.relationship('User_individual_MV', backref='users', uselist=False,
primaryjoin='User.id==User_individual_MV.id', foreign_keys='User_individual_MV.id')
individual.py
class Individual(db.Model):
__tablename__ = 'individuals'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
first_name = db.Column(db.String(100), unique=False, index=False, nullable=False)
last_name = db.Column(db.String(100), unique=False, index=False, nullable=False)
users = db.relationship('User',
lazy='subquery',
backref=db.backref('individuals', lazy='select'))
mat_view_factory.py
from sqlalchemy.ext import compiler
from sqlalchemy.schema import DDLElement, PrimaryKeyConstraint
from app import db
class CreateMaterializedView(DDLElement):
def __init__(self, name, selectable):
self.name = name
self.selectable = selectable
#compiler.compiles(CreateMaterializedView)
def compile(element, compiler, **kw):
# Could use "CREATE OR REPLACE MATERIALIZED VIEW..."
# but I'd rather have noisy errors
return 'CREATE MATERIALIZED VIEW %s AS %s' % (
element.name,
compiler.sql_compiler.process(element.selectable, literal_binds=True),
)
def create_mat_view(name, selectable, metadata=db.metadata):
_mt = db.MetaData() # temp metadata just for initial Table object creation
t = db.Table(name, _mt) # the actual mat view class is bound to db.metadata
for c in selectable.c:
t.append_column(db.Column(c.name, c.type, primary_key=c.primary_key))
if not (any([c.primary_key for c in selectable.c])):
t.append_constraint(PrimaryKeyConstraint(*[c.name for c in selectable.c]))
db.event.listen(
metadata, 'after_create',
CreateMaterializedView(name, selectable)
)
#db.event.listens_for(metadata, 'after_create')
def create_indexes(target, connection, **kw):
for idx in t.indexes:
idx.create(connection)
db.event.listen(
metadata, 'before_drop',
db.DDL('DROP MATERIALIZED VIEW IF EXISTS ' + name)
)
return t
def refresh_mat_view(name, concurrently):
# since session.execute() bypasses autoflush, must manually flush in order
# to include newly-created/modified objects in the refresh
db.session.flush()
_con = 'CONCURRENTLY ' if concurrently else ''
db.session.execute('REFRESH MATERIALIZED VIEW ' + _con + name)
def refresh_all_mat_views(concurrently=True):
'''Refreshes all materialized views. Currently, views are refreshed in
non-deterministic order, so view definitions can't depend on each other.'''
mat_views = db.inspect(db.engine).get_view_names(include='materialized')
for v in mat_views:
refresh_mat_view(v, concurrently)
class MaterializedView(db.Model):
__abstract__ = True
#classmethod
def refresh(cls, concurrently=True):
'''Refreshes the current materialized view'''
refresh_mat_view(cls.__table__.fullname, concurrently)
User_individual_MV.py:
from app import db
from app.mat_view_factory import MaterializedView, create_mat_view
from app.models import User, Individual
class User_individual_MV(MaterializedView):
__table__ = create_mat_view('user_individual_mv',
db.select(
[User.id.label('id'),
User.username.label('username'),
User.role_id.label('role_id'),
Individual.id.label('individual_id'),
Individual.first_name.label('first_name'),
Individual.last_name.label('last_name')
]
).select_from(db.join(User, Individual, isouter=False))
).group_by(Individual.last_name)
)
db.Index('uq_user_individual_mv', User_individual_MV.id, unique=True)
Any advice will be greatly appreciated.

How to build 'many-to-many' rrelationship in SQLAlchemy

I'm looking to have a 'many-to-many' relationship between users and products. For this, I prepared another table product_user but it is not working well. I can't use it in secondary relationship.
Error:
NameError: name 'product_users' is not defined
This is code:
## Product model
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
users = db.relationship("User", secondary=product_users, backref="users", lazy="dynamic")
def __repr__(self):
return '<Product %r>' % self.uid
## User model
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
products = db.relationship("Product", secondary=product_users, backref="products", lazy="dynamic")
def __repr__(self):
return '<User %r>' % self.uid
## Product User model
class ProductUser(db.Model):
__tablename__ = 'product_users'
id = db.Column(db.Integer, primary_key=True)
product_id = db.Column(db.Integer,db.ForeignKey('products.id'))
user_id = db.Column(db.Integer,db.ForeignKey('users.id'))
product = db.relationship("Product", backref='products', foreign_keys=[product_id])
user = db.relationship("User", backref='users', foreign_keys=[user_id])
def __repr__(self):
return '<ProductUser %r>'
Sorry I have typo table name.
from:
users = db.relationship("User", secondary=product_users, backref="users", lazy="dynamic")
products = db.relationship("Product", secondary=product_users, backref="products", lazy="dynamic")
to:
users = db.relationship("User", secondary='product_users', backref="users", lazy="dynamic")
products = db.relationship("Product", secondary='product_users', backref="products", lazy="dynamic")

nonSQL related attribute add to SQLAlchemy model error

I'm developing a web app with python and flask. I use Flask, SQLAlchemy and PostgreSQL for development. I have many-to-one related models. By this models one company can have many users but each user can only have one company.
models.py
class Company(ResourceMixin, db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, index=True,
nullable=False, server_default='')
phone = db.Column(db.String(24))
email = db.Column(db.String(255), index=True)
address = db.Column(db.String(255))
# Relations
users = db.relationship('User', backref='company')
class User(UserMixin, ResourceMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# User details
name = db.Column(db.String(50), index=True)
phone = db.Column(db.String(24))
address = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True, index=True, nullable=False,
server_default='')
password = db.Column(db.String(128), nullable=False, server_default='')
# Relations
company_id = db.Column(db.Integer, db.ForeignKey('companies.id',
onupdate='CASCADE',
ondelete='SET NULL'),
index=True)
views.py
app.route('/')
def index():
company = Company.query.get(1)
flash(company.name, company.user_count)
return render_template('index.html')
Error summary: "user_count" attribute is not part of the Company model.
I want to get the number of the users dynamically from Company model. Attribute should count users on each call of the model and serve it on a regular attribute (like company.user_count). I made it by creating a class method and calling it in view function but i want it to make the process automatic without calling method prior to use attribute.
I tried init function like this:
def __init__(self):
self.user_count = len(self.users)
And like this:
def __init__(self):
self.status()
def status(self):
self.user_count = len(self.users)
return True
And like this:
def __init__(self):
self.status()
#classmethod
def status(self):
self.user_count = len(self.users)
return True
all three versions throws same error. How can i overcome the problem.
Thanks a lot!
You can use a property:
class User(Base):
...
#property
def user_count(self):
return len(self.users)

How to add a custom function/method in sqlalchemy model to do CRUD operations?

Below I have a Flask-SQLAlchemy model for the table User.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
def add_user(self, name):
self.name = name
Here add_user is a custom method. So if I call the add_user method it should add the name to the User table.
Likewise how do I write custom methods for CRUD operations in that model itself?
You'll probably want to use a classmethod to accomplish this.
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
This way you can use User.create(name="kumaran") to create a new user that will be committed to the database.
Better yet, it is a great idea to create a mixin for this method and others like it so that the functionality can be easily reused in your other models:
class BaseMixin(object):
#classmethod
def create(cls, **kw):
obj = cls(**kw)
db.session.add(obj)
db.session.commit()
You can then reuse this functionality in your models by using multiple inheritance, like so:
class User(BaseMixin, db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self, name):
self.name = name
Not sure this is relevant to Flask-SQLAlchemy, but basic SQLAlchemy has examples of creating Mixin classes or augmenting the Base class.
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/mixins.html
e.g.
from sqlalchemy.ext.declarative import declared_attr
class MyMixin(object):
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
__mapper_args__= {'always_refresh': True}
id = Column(Integer, primary_key=True)
class MyModel(MyMixin, Base):
name = Column(String(1000))
I would accomplish what you're after like this:
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.text("CURRENT_TIMESTAMP"))
def __init__(self):
#self.name = name
self.name = None
#classmethod
def add_user(cls, session, name):
user = User(name)
session.add(user)
return User
Then in whatever context you're using it in, create a session, call your method, and commit it.
from .user import User
session = Session()
# or if using Flask SQLAlchemy
# session = db.session
User.add_user(session, 'Foo')
session.commit()
From the sqlalchemy docs:
Keep the lifecycle of the session (and usually the transaction) separate and external.
In contrast to one of the other answers, which assumes you are using this model in a Flask app with FlaskSQLAlchemy's global db.session object, parametrizing the session object like this keeps your model code separate from your session management code. This allows it to be used flexibly in many different contexts.

Flask with SQLAlchemy Many-to-many Relationship w/ Mixins

The problem I face is the following: in the following block of code you can see that I want to separate the id, created_on and updated_on attributes so that I follow the Don't Repeat Yourself (DRY) principle.
# Used to generalize a default database model with an ID
class IdentifierMixin(object):
id = db.Column(db.Integer, primary_key=True)
# Used to generalize timestamps on the default database model
class TimestampMixin(object):
created_on = db.Column(db.DateTime, server_default=db.func.now())
updated_on = db.Column(db.DateTime, server_default=db.func.now(),
onupdate=db.func.now())
# The many-to-many relationships
users_roles = db.Table('users_roles',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class User(declarative_base(), IdentifierMixin, TimestampMixin, UserMixin):
__tablename__ = 'user'
# Relationships
roles = db.relationship('Role', secondary=users_roles,
backref=db.backref('users', lazy='dynamic'))
uploads = db.relationship('Upload', backref='uploader', lazy='dynamic')
email = db.Column(db.String(100), nullable=False, unique=True)
username = db.Column(db.String(40), nullable=True, unique=True)
password = db.Column(db.String, nullable=False)
authenticated = db.Column(db.Boolean, default=False)
# True, as all users are active
def is_active(self):
return True
# Return the email address to satisfy Flask-Login's requirements
def get_id(self):
return self.email
# Return the authenticated parameter
def is_authenticated(self):
return self.authenticated
# False, anonymous users are not supported
def is_anonymous(self):
return False
def __repr__(self):
if self.username is not None:
return '<User %r>' % self.username
else:
return '<User %r>' % self.email
class Role(declarative_base(), IdentifierMixin, RoleMixin):
__tablename__ = 'role'
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
However, it seems that the id attribute (inherited from the IdentifierMixin mixin) cannot be found:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'users_roles.user_id' could not find table 'user' with which to generate a foreign key to target column 'id'
Can someone please explain what I am doing wrong? I would appriciate it immensely. With regards, Tim.

Categories

Resources