Reputation: 177
I'm working on a huge code base that does all database interactions as following:
ExampleClass.query.all()
Where the ExampleClass
inherits the properties of db.Model
and our BaseEntity
defined as following:
class BaseEntity(object):
deleted = db.Column(db.DateTime, default=None, nullable=True)
... # more columns
Now when i query the ExampleClass
for all entries, ExampleClass.query.all()
, i want the entries where the deleted field is set to a date, aka the entry is removed from the system to be excluded. It would be ideal if i wouldn't have to update the entire codebase with a simple .filter(deleted != None)
. My solution for this was adding a SQLAlchemy event filter: before_compile
. The documentation gave me exactly what is was searching for:
@event.listens_for(Query, "before_compile", retval=True)
def no_deleted(query):
for desc in query.column_descriptions:
if desc['type'] is ExampleClass:
entity = desc['expr']
query = query.filter(entity.deleted == False)
return query
However i cannot get this to work, the last error i got stuck on is as following:
AttributeError: 'Mapper' object has no attribute 'deleted'
Versions of the libraries I use are: Flask==0.10.1, Flask-SQLAlchemy==1.0, SQLAlchemy==1.0.6
Upvotes: 5
Views: 1806
Reputation: 531
I know it is old question, but recently I make it work by this function:
@event.listens_for(Query, "before_compile", retval=True)
def no_deleted(query):
for desc in query.column_descriptions:
if desc['type'] is ExampleClass:
entity = desc['entity']
limit, offset = query._limit, query._offset
query._limit, query._offset = None, None
query = query.filter(entity.deleted == False)
query._limit, query._offset = limit, offset
return query
You need to do the filter on entity, not type. And temporary remove limit and offset.
Flask==1.0.2, Flask-SQLAlchemy==2.3.2, SQLAlchemy==1.2.12
link to sqlalchemy docs for this event
if you want to be able to not exclude deleted items there is this option:
Create QueryMixin:
class ShowDeleted:
def show_deleted(self):
q = self._clone()
q.__dict__['__show_deleted'] = True
return q
edit no_deleted function
def no_deleted(query):
for desc in query.column_descriptions:
show_deleted = query.__dict__.get('__show_deleted', False)
if not show_deleted and desc['type'] is ExampleClass:
entity = desc['entity']
limit, offset = query._limit, query._offset
query._limit, query._offset = None, None
query = query.filter(entity.deleted == False)
query._limit, query._offset = limit, offset
return query
and if you are using FlaskSQLAlchemy, setup db this way:
from flask_sqlalchemy import SQLAlchemy, BaseQuery
db = SQLAlchemy(query_class= type('Query', (ShowDeleted, BaseQuery), {}))
in case of raw sqlalchemy use this snippet:
from sqlalchemy.orm import Query
db = scoped_session(sessionmaker(query_cls=type('Query', (ShowDeleted, Query), {})))
now you can in your code call
db.session.query(ExampleClass).show_deleted().all()
Upvotes: 4
Reputation: 937
The obvious and easy approach to do this (tested and works for me) would be:
class BaseEntity(db.Model):
deleted = db.Column(db.DateTime, default=None, nullable=True)
BaseEntity.query = BaseEntity.query.filter(BaseEntity.deleted == None)
That said, other developers might expect that BaseEntity.query
starts with a clean query object so you might consider using a different class property than query
:
class BaseEntity(db.Model):
deleted = db.Column(db.DateTime, default=None, nullable=True)
BaseEntity.non_deleted = BaseEntity.query.filter(BaseQuery.deleted == None)
# and then use it like …
BaseEntity.non_deleted.all()
Upvotes: -1