SQLAlchemy JSONB and UUID filtering - python

Gyus, I have two models here(their bodies are not full here, just needed fields) in my Flask application.
class Action(db.Model):
__tablename__ = 'action'
id = db.Column(psql.UUID(as_uuid=True), default=uuid4, primary_key=True)
action_data = db.Column(JSONB, nullable=False)
class Post(CardS3Mixin, db.Model, Base):
__tablename__ = 'post'
id = db.Column(psql.UUID(as_uuid=True), default=uuid4, primary_key=True)
user_id = db.Column(psql.UUID(as_uuid=True), db.ForeignKey('user.id', ondelete='CASCADE'))
title = db.Column(db.Text)
description = db.Column(db.Text, nullable=True)
In some records in Action table there are records with action_data be like (with post_id):
{
"post_id": "97e9fc4d-97e7-43fa-abbd-76b2de2f9395",
"user_id": "6861765a-f55a-43e2-8d77-e8c48a5aea90",
"feed_id": "02e4a972-2e0f-4011-8bc3-7430a0c3056a"
}
or without post_id
{
"user_id": "741686bd-40b6-4ca1-b972-a346c60404dc",
"like_id": "485f2c4b-73cc-4fcb-a809-81e5f018327d",
}
and some of Posts are already deleted, so I want to clean actions about these posts.
I wrote an SQL-query:
SELECT action.id, action.action_data->>'post_id' as act_data, post.id as post_id
FROM action
LEFT JOIN post
ON (action.action_data->>'post_id')::uuid = post.id
WHERE action.action_data->>'post_id' IS NOT NULL AND post.id IS NULL
It returns me 93 records and it's correct.
So, I'm trying to translate it into SQLAlchemy query, like that:
def foo():
post_actions = session.query(Action).join(
Post,
Action.action_data['post_id'].astext == Post.id,
isouter=True
)
post_actions = post_actions.filter(
and_(
Action.action_data['post_id'].astext.isnot(None),
Post.id.is_(None)
)
).all()
And it returns me 2826 records, that's incorrect and there're 2826 with 'post_id' in 'action_data', it looks like Post.id.is_(None) in filters is ignored, but when I put a breakepoint and printes Query, generated by SQLAlchemy, I saw that:
SELECT action.id AS action_id, action.action_data AS action_action_data
FROM action LEFT OUTER JOIN card ON (action.action_data ->> %(action_data_1)s) = %(param_1)s
WHERE (action.action_data -> %(action_data_2)s) IS NOT NULL AND post.id IS NULL
I'm completely not understanding, where my fault is..

Related

Flask SQLAlchemy query information in many to many relationship

I want to query the DB filtered by my order_id and have access to the information for the category_name.
I've tried reading through several answers like this one, but I can't quite get to where I want to be. I have a models such as:
models.py
product_category_table = db.Table('prouct_to_category_association',
db.Column('product_id', db.Integer, db.ForeignKey('product.id')),
db.Column('category_id', db.Integer, db.ForeignKey('category.id')))
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), index=True)
risks = db.relationship("Product",
secondary=product_category_table,
back_populates="categories")
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, db.ForeignKey('Order.id'))
product_value = db.Column(db.Float(1,2), index=True)
categories = db.relationship("Category",
secondary=risk_category_table,
back_populates="risks")
My existing query looks like:
products = Product.query.filter_by(order_id = 1).all()
and when I want to access the information I am using (in Jinja2 format):
{% for product in products %}
cat = {{product.categories}}, val={{product.product_value}}
{% endfor %}
This allows me to print:
cat = [1], val = 5.99, cat = [2], val = 6.55, and so on...
However, what I can't figure out is how I can access not just the category.id, but the actual category.name... I understand why I am getting it category.id (from the association table).
Questions
How do I update my query so the return will have this information?
How do I reference it in Jinja?
you can make backref in category table
risks = db.relationship("Product",
secondary=product_category_table,
back_populates="categories",backref=db.backref('cat'))
access it using product.cat.name or product.cat.risks in jinja2
after using backref you can access all the rows of Categories

Searching for posts with tags using Flask-SQLAlchemy

I am developing an API using Flask-SQLAlchemy. I have a database containing posts, tags, and the many-to-many mapping between them. I want to look up posts that have at least one matching tag in a search. For example, a search for tags "A", "B", and "C" should return posts that have "A" or "B" or "C" as their tags.
I am roughly following Charles Leifer's example of querying multiple tags when using a many-to-many relationship. My current query, however, does not return any rows at all:
#app.route('/api/posts/search', methods=['GET'])
def search_posts():
tags = json.loads(request.data['tags'])
posts = db.session.query(Post).join(TagMap).join(Tag).filter(Tag.tag in tags).group_by(Post.id).all()
return jsonify(posts)
I have confirmed that the tags enter the database correctly as a list of strings, and that I have posts, tags, and the mapping between them correctly stored in the database.
If necessary, my models are as follows:
#dataclass
class Post(db.Model):
__tablename__ = 'post'
id:int
text:str
created:str
op:bool
id = db.Column(
db.Integer,
primary_key=True
)
text = db.Column(
db.String(2000),
index=False,
nullable=False
)
#dataclass
class Tag(db.Model):
__tablename__ = 'tag'
id:int
tag:str
id = db.Column(
db.Integer,
primary_key=True
)
tag = db.Column(
db.String(32),
index=False,
nullable=False,
unique=True
)
#dataclass
class TagMap(db.Model):
__tablename__ = 'tagmap'
post_id:int
tag_id:int
id = db.Column(
db.Integer,
primary_key=True
)
post_id = db.Column(
db.ForeignKey('post.id'),
index=False,
nullable=False
)
tag_id = db.Column(
db.ForeignKey('tag.id'),
index=False,
nullable=False
)
Seems to me like it's an issue with your in check. You currently have:
Tag.tag in tags
Which will be first evaluated in Python to a boolean value. Which will always be False because Tag.tag is not a string, and so can never be "in tags". This effectively generates a WHERE 0 = 1 (may differ depending on DB) in your SQL query.
You need to use the "SQL-in" by rewriting it as:
Tag.tag.in_(tags)

Flask-appbuilder model column default value set from g.user does not work

In my application I would like all tables save and read data by Company Id.
All tables have FK to Company Id. I've override Login and Registration views to Login by providing Company, User and Password. Company Id is shown in "g.user.cia_id_fk" after user logged-in. cia_id_fk has been added to "ab_user"
enter image description here
My approach is to "default" cia_id_fk to "g.user.cia_id_fk" in the Model.
I am following "extendsecurity2" example (https://github.com/dpgaspar/Flask-AppBuilder/blob/master/examples/extendsecurity2/app/models.py) where "Contact" model Created_by column is defaulted to "g.user.id"
model.py
class Company(Model):
__tablename__ = "company"
id = Column(Integer, primary_key=True)
code = Column(String(15), unique=True, nullable=False)
name = Column(String(60), unique=False, nullable=False)
class AlpUser(User):
__tablename__ = 'ab_user'
cia_id_fk = Column(Integer, ForeignKey("company.id"), nullable=False)
class Status(Model):
__tablename__ = "WorkItemStatus"
id = Column(Integer, primary_key=True)
status = Column(String(30), nullable=False)
#declared_attr
def cia_id_fk(cls):
return Column(
Integer, ForeignKey("company.id"), default=cls.get_cia_id, nullable=False
)
#declared_attr
def cia_id(cls):
return relationship(
"Company",
primaryjoin="%s.cia_id_fk == Company.id" % cls.__name__,
enable_typechecks=False,
)
#classmethod
def get_cia_id(cls):
try:
return g.user.cia_id
except Exception:
return None
When try to add a record in "status" I get this error:
Note that field "cia_id_fk" is not shown in the form.
integrity error: (sqlite3.IntegrityError) NOT NULL constraint failed: status.cia_id_fk
[SQL: INSERT INTO "status" (status, cia_id_fk) VALUES (?, ?)]
[parameters: ('pending', None)]
This indicate that "default" did not work. I'd tried may variations but not luck. Note that Example mention above works fine but design is different and I would like something simpler.
Any other approach (like set in pre form) is welcome.
Thanks in advance for any help

sqlalchemy foreign keys / query joins

Hi im having some trouble with foreign key in sqlalchemy not auto incrementing on a primary key ID
Im using: python 2.7, pyramid 1.3 and sqlalchemy 0.7
Here is my models
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, ForeignKey('mapper.object_id'), autoincrement=True, primary_key=True)
title = Column(String(30), unique=True)
title_slug = Column(String(75), unique=True)
text = Column(Text)
date_added = Column(DateTime)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True)
email = Column(String(100), unique=True)
password = Column(String(100))
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True)
class Member(Base):
__tablename__ = 'members'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'), primary_key=True)
class Resource(Base):
__tablename__ = 'resource'
id = Column(Integer, primary_key=True)
tablename = Column(Text)
action = Column(Text)
class Mapper(Base):
__tablename__ = 'mapper'
resource_id = Column(Integer, ForeignKey('resource.id'), primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'), primary_key=True)
object_id = Column(Integer, primary_key=True)
and here is my RAW SQL query which i've written in SQLAlchemys ORM
'''
SELECT g.name, r.action
FROM groups AS g
INNER JOIN resource AS r
ON m.resource_id = r.id
INNER JOIN page AS p
ON p.id = m.object_id
INNER JOIN mapper AS m
ON m.group_id = g.id
WHERE p.id = ? AND
r.tablename = ?;
'''
obj = Page
query = DBSession().query(Group.name, Resource.action)\
.join(Mapper)\
.join(obj)\
.join(Resource)\
.filter(obj.id == obj_id, Resource.tablename == obj.__tablename__).all()
the raw SQL Query works fine without any relations between Page and Mapper, but SQLAlchemys ORM seem to require a ForeignKey link to be able to join them. So i decided to put the ForeignKey at Page.id since Mapper.object_id will link to several different tables.
This makes the SQL ORM query with the joins work as expected but adding new data to the Page table results in a exception.
FlushError: Instance <Page at 0x3377c90> has a NULL identity key.
If this is an auto- generated value, check that the database
table allows generation of new primary key values, and that the mapped
Column object is configured to expect these generated values.
Ensure also that this flush() is not occurring at an inappropriate time,
such as within a load() event.
here is my view code:
try:
session = DBSession()
with transaction.manager:
page = Page(title, text)
session.add(page)
return HTTPFound(location=request.route_url('home'))
except Exception as e:
print e
pass
finally:
session.close()
I really don't know why, but i'd rather have the solution in SQLalchemy than doing the RAW SQL since im making this project for learning purposes :)
I do not think autoincrement=True and ForeignKey(...) play together well.
In any case, for join to work without any ForeignKey, you can just specify the join condition in the second parameter of the join(...):
obj = Page
query = DBSession().query(Group.name, Resource.action)\
.join(Mapper)\
.join(Resource)\
.join(obj, Resource.tablename == obj.__tablename__)\
.filter(obj.id == obj_id)\
.all()

sqlalchemy: how to join several tables by one query?

I have the following SQLAlchemy mapped classes:
class User(Base):
__tablename__ = 'users'
email = Column(String, primary_key=True)
name = Column(String)
class Document(Base):
__tablename__ = "documents"
name = Column(String, primary_key=True)
author = Column(String, ForeignKey("users.email"))
class DocumentsPermissions(Base):
__tablename__ = "documents_permissions"
readAllowed = Column(Boolean)
writeAllowed = Column(Boolean)
document = Column(String, ForeignKey("documents.name"))
I need to get a table like this for user.email = "user#email.com":
email | name | document_name | document_readAllowed | document_writeAllowed
How can it be made using one query request for SQLAlchemy? The code below does not work for me:
result = session.query(User, Document, DocumentPermission).filter_by(email = "user#email.com").all()
Thanks,
Try this
q = Session.query(
User, Document, DocumentPermissions,
).filter(
User.email == Document.author,
).filter(
Document.name == DocumentPermissions.document,
).filter(
User.email == 'someemail',
).all()
As #letitbee said, its best practice to assign primary keys to tables and properly define the relationships to allow for proper ORM querying. That being said...
If you're interested in writing a query along the lines of:
SELECT
user.email,
user.name,
document.name,
documents_permissions.readAllowed,
documents_permissions.writeAllowed
FROM
user, document, documents_permissions
WHERE
user.email = "user#email.com";
Then you should go for something like:
session.query(
User,
Document,
DocumentsPermissions
).filter(
User.email == Document.author
).filter(
Document.name == DocumentsPermissions.document
).filter(
User.email == "user#email.com"
).all()
If instead, you want to do something like:
SELECT 'all the columns'
FROM user
JOIN document ON document.author_id = user.id AND document.author == User.email
JOIN document_permissions ON document_permissions.document_id = document.id AND document_permissions.document = document.name
Then you should do something along the lines of:
session.query(
User
).join(
Document
).join(
DocumentsPermissions
).filter(
User.email == "user#email.com"
).all()
One note about that...
query.join(Address, User.id==Address.user_id) # explicit condition
query.join(User.addresses) # specify relationship from left to right
query.join(Address, User.addresses) # same, with explicit target
query.join('addresses') # same, using a string
For more information, visit the docs.
A good style would be to setup some relations and a primary key for permissions (actually, usually it is good style to setup integer primary keys for everything, but whatever):
class User(Base):
__tablename__ = 'users'
email = Column(String, primary_key=True)
name = Column(String)
class Document(Base):
__tablename__ = "documents"
name = Column(String, primary_key=True)
author_email = Column(String, ForeignKey("users.email"))
author = relation(User, backref='documents')
class DocumentsPermissions(Base):
__tablename__ = "documents_permissions"
id = Column(Integer, primary_key=True)
readAllowed = Column(Boolean)
writeAllowed = Column(Boolean)
document_name = Column(String, ForeignKey("documents.name"))
document = relation(Document, backref = 'permissions')
Then do a simple query with joins:
query = session.query(User, Document, DocumentsPermissions).join(Document).join(DocumentsPermissions)
Expanding on Abdul's answer, you can obtain a KeyedTuple instead of a discrete collection of rows by joining the columns:
q = Session.query(*User.__table__.columns + Document.__table__.columns).\
select_from(User).\
join(Document, User.email == Document.author).\
filter(User.email == 'someemail').all()
This function will produce required table as list of tuples.
def get_documents_by_user_email(email):
query = session.query(
User.email,
User.name,
Document.name,
DocumentsPermissions.readAllowed,
DocumentsPermissions.writeAllowed,
)
join_query = query.join(Document).join(DocumentsPermissions)
return join_query.filter(User.email == email).all()
user_docs = get_documents_by_user_email(email)

Categories

Resources