Formalchemy - form for one to many relation - sqlalchemy

I've got simple 'one-to-many' relation written with sqlalchemy:
class Product(Base, Asset):
description = Column(sa.Text)
image = Column(sa.VARCHAR(length=150))
package_type_prices = relationship("ProductPackageTypePrice")
class ProductPackageTypePrice(Base, Asset):
id = Column(sa.Integer, primary_key=True, nullable=False)
id_product = Column(sa.Integer, ForeignKey("assets.product.id"))
package_type = Column(package_type_enum, nullable=False)
price = Column(sa.DECIMAL(10,2), nullable=False)
My question is how to (using formalchemy) generate form with fields for Product but also for already created ProductPackageTypePrice's and fields to add new one?

Related

How to use Sqlalchemy ORM to just get related ids not the full record of an association in a many-to-many?

Is there a way to just get the related category ids of a many-to-many association - without getting the full related category record?
Code being used currently is:
products = session.query(Product).join(association_table).filter(association_table.c.category_id == category)
for product in products:
print(product.product_id, [item.id for item in product.categories])
With the models:
association_table = Table(
"product_category_association_table",
Base.metadata,
Column("product_id", ForeignKey("product.id"), primary_key=True),
Column("category_id", ForeignKey("category.id"), primary_key=True),
)
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True)
categories = relationship(
"Category", secondary=association_table, back_populates="products"
)
product_id = Column(String(length=50), unique=True)
label = Column(String, nullable=False)
class Category(Base):
__tablename__ = "category"
id = Column(Integer, primary_key=True)
products = relationship(
"Product", secondary=association_table, back_populates="categories"
)
active = Column(Boolean, default=True)
label = Column(String, nullable=False)
This emits the initial SQL to get all products.
Then for each loop is does a query to the related category table (which is not needed as all that is wanted is the category ids).
Is this possible to achieve without the join over to the category table?

Convenience of relationships, where relationships not possible?

I have three objects (Leads, MarketingMails, WebformSubmissions). I'd like to query for objects related to each other through a join. Example:
class MarketingMail(db.Model):
mailid = db.Column(db.Text, nullable=False, primary_key=True)
address = db.Column(db.Text, nullable=False)
name = db.Column(db.Text, nullable=False)
# Maps (nearly all the time) to one MarketingMail. Sometimes 2.
# contains ~0.1% duplicates
personal_url = db.Column(db.Text, nullable=False)
class WebformSubmission(db.Model):
id = db.Column(db.Integer, primary_key=True)
# Maps (nearly all the time) to one MarketingMail. Sometimes 2.
# contains ~0.1% duplicates
submission_url = db.Column(db.Text, nullable=False)
name = db.Column(db.Text, nullable=False)
email = db.Column(db.Text, nullable=False)
class Lead(db.Model):
id = db.Column(db.Integer, primary_key=True)
# References one and only one MarketingMail
# contains ~0.1% duplicates
marketing_mail_piece = db.Column(db.Text, nullable=False)
name = db.Column(db.Text, nullable=False)
agent = db.Column(db.Text, nullable=False)
phone = db.Column(db.Text, nullable=False)
status = db.Column(db.Text, nullable=False)
I'd like to be able to query for:
WebformSubmissions, starting with a Lead, via Mail
Along the lines of: Lead.Mail.WebformSubmissions
Mail, starting with a WebformSubission.
Along the lines of: WebformSubmissions.Mail
Leads, starting with a WebformSubmission, via Mail
Along the lines of: WebformSubmissions.Mail.Leads
The problem is that for the Mail<--->Webform relationship, neither of the relevant columns are distinct, so I think I can only jump between them using joins.
Is there any way to set up joins on the objects so that I get the convenient API and call-structure that relationships normally provides?

SQLAlchemy relationship selection criteria

I have two model classes:
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
class Program_Session(db.Model):
__tablename__ = "program_session"
id = db.Column(db.Integer, primary_key=True)
session_title = db.Column(db.String(100), nullable=False)
session_description = db.Column(db.String(100))
session_year = db.Column(db.Integer)
program_id = db.Column(db.Integer, db.ForeignKey("programs.id"), nullable=False)
students = db.relationship('Student_Registration', backref='program_session')
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
I create an object of Programs with:
program = Programs.query.first()
Now I can access all the Program_Sessions from the selected Program:
print(pro.program_sessions)
Is it possible to subquery/query to retrieve only those Program_session in Program whose year is 2021?
Option-1: filter on 'python' (in memory)
Once you get all Program_Sessions (all_sessions = pro.program_sessions), you filter them by sessions_2021 = [item for item in all_sessions if item.session_year == 2021].
Needless to say, this is not efficient at all as lots of data will be loaded from the database to be immediately discarded.
Option2: use Dynamic Relationship Loaders
Define the relationship with lazy="dynamic", which will return a Query and hence you will be able to apply additional criteria to the query:
class Programs(db.Model):
# ...
program_sessions = db.relationship('Program_Session', backref='programs', lazy="dynamic")
program = Programs.query.first()
sessions_2021 = program.program_sessions.filter(Program_Session.year == 2021).all()
Option3: use orm.with_parent [BEST]
sessions_2021 = select(Program_Session).where(with_parent(program, Program_Session.programs)).where(Program_Session.year == 2021)
The answer is yes...
van's answer shows you options for playing with sqlalchemy's query mechanism. But what if you want to write this logic on the Programs class itself? That way anywhere you have a Programs object, you can access the filter.
You can do it in pretty plain python by altering the Programs class like so:
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
'''Here I add a filter that returns only the sessions for a particular year
'''
def program_sessions_by_year(self, year):
return filter(lambda ps: ps.session_year == year, self.program_sessions)
If you care about efficiency, you can get the database to do the filtering for you using a bit more sqlalchemy magic:
from sqlalchemy.orm import object_session
class Programs(db.Model):
__tablename__ = "programs"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(100), nullable=False)
duration = db.Column(db.Integer, nullable=False)
date_created = db.Column(db.DATE, default=datetime.now())
created_by = db.Column(db.String(100))
program_sessions = db.relationship('Program_Session',backref='programs')
'''Improve efficiency by using DB's SQL engine to filter the object.
'''
def program_sessions_by_year(self, year):
return object_session(self)\
.query(Program_Session)\
.filter_by(session_year=year, program_id=self.id)\
.all()
Either way you can then write (where-ever you have a Program object):
# lets say you just want the first program
first_program = Programs.query.first()
# to get the program sessions by year 2021
first_program.program_sessions_by_year(2021)
There's probably a bunch of other ways you could do something like this. SqlAlchemy is a big library. For more background on my answer, have a look at the SQL expressions as Mapped Attributes docs.

Can I specify the join conditions of a relationship when writing a query instead of doing so in my model class?

I have three models...
class Customer(db.Model, TimestampMixin):
pk = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
class User(db.Model, TimestampMixin):
pk = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text, nullable=False)
class CustomerUserStat(db.Model):
__table_args__ = (
UniqueConstraint("customer_pk", "user_pk", name="customer_pk_user_pk"),
)
pk = db.Column(db.Integer, primary_key=True)
customer_pk = db.Column(db.Integer, db.ForeignKey("customers.pk"), nullable=False)
user_pk = db.Column(db.Integer, db.ForeignKey("users.pk"), nullable=False)
data = db.Column(db.Integer, nullable=False, default=0)
I’ve tried...
m.Customer.query.outerjoin(
m.CustomerUserStat,
(m.CustomerUserStat.user_pk == entity.pk)
& (m.CustomerUserStat.customer_pk == m.Customer.pk)
).add_entity(m.CustomerUserStat)
But that gives me a tuple with a Customer object and a CustomerUserStat (or None if one doesn’t exist), close but not quite what I am looking for.
I’ve also tried adding a userstats relationship to the Customer model and ...
m.Customer.query.outerjoin(m.Customer.userstats
).filter(m.CustomerUserStat.user_pk == user.pk
).options(contains_eager(m.Customer.userstats))
But that didn’t produce any results if the user's CustomerUserStat record was missing.
Ultimately, I would like to write a query that will give me a list of Customer objects with an attribute that has an instance of CustomerUserStat for a user that I specify in the query and I need to be able to order by the CustomerUserStat.data field and paginate the results.
It looks to me like you are looking for the AssociationProxy pattern
I came up with this hack to get the query that I needed. In my Flask view function, I defined a new class that inherits from the original and added the relationship to it. Then I use the new class to create the query.
class TempCustomer(m.Customer):
userstats = db.relationship(
m.CustomerUserStat,
primaryjoin=(m.Customer.pk == m.CustomerUserStat.customer_pk) &
(m.CustomerUserStat.user_pk == agent.pk),
uselist=False,
viewonly=True,
lazy="joined",
)
q = TempCustomer.query.outerjoin(m.CustomerUserStat, (m.Customer.pk ==
m.CustomerUserStat.customer_pk) & (m.CustomerUserStat.user_pk == agent.pk),)
This works for me, but doesn't seem like the best solution.
It also results in two LEFT JOINS for the same data which is probably not the most efficient.

sql alchemy: return all unique types of great great grandchildren

I have 6 tables. I am essentially trying to return all unique types of great great grandchildren
How do I return a list of all the unique types of sku_numbers in a FreightDomesticOrder?
Table Definitions:
class FreightOrderDomestic(db.Model):
"""
A shipment of products from a manufacturer to a fulfillment center
"""
__tablename__ = 'Freight_Order_Domestic'
id = db.Column(db.Integer, primary_key=True, nullable=False)
class Pallet(db.Model):
"""
An individual Pallet (full of individual cases)
"""
__tablename__ = 'Pallet'
id = db.Column(db.Integer, primary_key=True, nullable=False)
freight_order_fkey = db.ForeignKey("Freight_Order_Domestic.id")
freight_order_id = db.Column(db.Integer, freight_order_fkey, nullable=False)
class OuterCase(db.Model):
"""
An outer case (full of inner cases)
"""
__tablename__ = 'Outer_Case'
id = db.Column(db.Integer, primary_key=True, nullable=False)
pallet_fkey = db.ForeignKey("Pallet.id")
pallet_id = db.Column(db.Integer, pallet_fkey, nullable=False)
class InnerCase(db.Model):
"""
An individual case (full of individual items)
"""
__tablename__ = 'Inner_Case'
id = db.Column(db.Integer, primary_key=True, nullable=False)
outer_case_fkey = db.ForeignKey("Outer_Case.id")
outer_case_id = db.Column(db.Integer, outer_case_fkey, nullable=False)
class Each(db.Model):
"""
An individual item
"""
__tablename__ = 'Each'
id = db.Column(db.Integer, primary_key=True, nullable=False)
inner_case_fkey = db.ForeignKey("Inner_Case.id")
inner_case_id = db.Column(db.Integer, inner_case_fkey, nullable=False)
sku_fkey = db.ForeignKey("Sku.id")
sku_id = db.Column(db.Integer, sku_fkey, nullable=False)
class Sku(db.Model):
"""
The SKU of an product, the attributes it should have to determine pricing
"""
__tablename__ = 'Sku'
id = db.Column(db.Integer, primary_key=True, nullable=False)
sku_number = db.Column(db.String(255), nullable=False)
Here is what I am trying so far but I am stuck, I am also wondering how cheap I can make this:
SKUs = Session.query(Pallet, Outer_case, Inner_case, Each, Sku).filter(Pallet.id == Outer_case.pallet_id).filter(Outer_case.id == Inner_case.outer_case_id).filter(Inner_case.id == Each.inner_case_id).filter(Each.sku_id == sku.id).all()
My other idea was to loop through all Pallets and then Outer_cases and so on but that seems too expensive.
Edited post after table definitions:
Given your table definitions, this should work:
SKUs = session.query(Sku.sku_number)
.join(Each).join(InnerCase)
.join(OuterCase).join(Pallet)
.join(FreightOrderDomestic)
.filter(FreightOrderDomestic.id == myOrderNumber)
.group_by(Sku).all()
However, looking at your table definitions I have some other comments that will hopefully help:
You should setup relationships between the tables, so you can easily work with the different objects. Check out the sqlalchemy documentation on relationships here
I would suggest reading up on Database Normalization. This will help you understand some of the below points
You currently have Each setup so there will be duplicate items of the same type if they are assigned to different InnerCases. This is not a good database practice. You should setup this relationship as a many to many relationship, and you can read about that here. This will allow you to have a list of items, and each item can link to many different InnerCases
FreightDomesticOrder should have a column for order number. You don't want to use a key value as an order number
If you are going to handle international orders also, you should probably just create a type field for FreightOrderDomestic and rename it to FreightOrder
Original Post:
Can you provide the table definitions? This is very hard to answer accurately without seeing the relationships you have setup. Something like this could work, if you setup your tables like I would have given the description you gave, or it could not work because you didn't provide enough info:
SKUs = session.query(Sku.sku_number)
.join(Each).join(Inner_case)
.join(Outer_case).join(Pallet)
.join(Freight_order)
.filter(Freight_order.order_number == myOrderNumber)
.group_by(Sku).all()