ForeignKey serialized as empty dict and not getting populated with data - flask

I've two models Profile & Product representing One-Many relationship. One profile can have many products. I'm serializing all the fields. The column which has ForeignKey is coming out to be empty dictionary. The following model will make my issue more clear.
from backend_olx import db
from marshmallow import Schema, fields
from datetime import datetime
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
created_by = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=False)
purchased_by = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=True)
name = db.Column(db.String(50), nullable=False)
price = db.Column(db.Integer, nullable=False)
description = db.Column(db.Text, nullable=False)
def __repr__(self):
return '<Product Name %r>' % self.name
class Profile(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
email = db.Column(db.String(100), nullable=False, unique=True)
products_sold = db.relationship('Product', backref='profile_sold', foreign_keys="Product.created_by",lazy=True)
products_purchased = db.relationship('Product', backref='profile_purchased', foreign_keys="Product.purchased_by",lazy=True)
def __repr__(self):
return '<User %r>' % self.username
class ProfileSchema(Schema):
id = fields.Int(dump_only=True)
username = fields.Str()
email = fields.Str()
products_sold = fields.Nested('ProductSchema', many=True)
products_purchased = fields.Nested('ProductSchema', many=True)
class ProductSchema(Schema):
id = fields.Int(dump_only=True)
name = fields.Str()
price = fields.Int()
created_by = fields.Nested('ProfileSchema')
purchased_by = fields.Nested('ProfileSchema')
profile_schema = ProfileSchema()
profiles_schema = ProfileSchema(many=True)
product_schema = ProductSchema()
products_schema = ProductSchema(many=True)
The Nested() method in ProfileSchema is working as expected but it is giving { } in ProductSchema.
I want created_by and purchased_by fields to be populated as well.
How to go about ths?

You can't just pass a foreign key and expect Nested to know what to do about it. You need to pass a relation instead.
Create a relation for both fields and use the relation name in the schema.
I typically use xxx_id for column name and xxx for relation name.
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
created_by_id = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=False)
purchased_by_id = db.Column(db.Integer, db.ForeignKey('profile.id'), nullable=True)
name = db.Column(db.String(50), nullable=False)
price = db.Column(db.Integer, nullable=False)
description = db.Column(db.Text, nullable=False)
# Setup relations here
created_by = db.relationship(...)
purchased_by = db.relationship(...)
class ProductSchema(Schema):
id = fields.Int(dump_only=True)
name = fields.Str()
price = fields.Int()
created_by = fields.Nested('ProfileSchema')
purchased_by = fields.Nested('ProfileSchema')

Related

SQLAlchemy filter all relationships

so I have these four tables
class ClimbingHallSectionModel(BaseModel):
__tablename__ = 'climbing_hall_section'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
climbing_hall_id = db.Column(db.Integer, db.ForeignKey('climbing_hall.id', ondelete='CASCADE'), nullable=False)
walls = db.relationship('ClimbingWallModel', lazy='select', passive_deletes=True)
class ClimbingWallModel(BaseModel):
__tablename__ = 'climbing_wall'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
climbing_hall_section_id = db.Column(db.Integer, db.ForeignKey('climbing_hall_section.id', ondelete='CASCADE'), nullable=False)
height = db.Column(db.Integer)
lines = db.relationship('LineModel', lazy='select', passive_deletes=True)
class LineModel(BaseModel):
__tablename__ = 'line'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
climbing_wall_id = db.Column(db.Integer, db.ForeignKey('climbing_wall.id', ondelete='CASCADE'), nullable=False)
routes = db.relationship('RouteModel', lazy='select', passive_deletes=True)
class RouteModel(BaseModel):
__tablename__ = 'route'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
active = db.Column(db.Boolean, nullable=False, default=True)
line_id = db.Column(db.Integer, db.ForeignKey('line.id', ondelete='CASCADE'), nullable=False)
I want to query all sections where the attribute active of the route table is true.
I tried the following query:
sections = (db.session.query(ClimbingHallSectionModel)
.join(ClimbingWallModel)
.join(LineModel)
.join(RouteModel)
.filter(RouteModel.active==True).all())
This query returns indeed only these sections which contains routes with the attribute active set to true.
However if I now acces the routes of one of the sections with sections.walls[0].lines[0].routes, I get all routes of the selected line including the routes where active is set to false.
For me it looks like that the filter only works correctly on the ClimbingHallSection level but then is not passed to the related tables.
Of course I could do something like this
sections = (db.session.query(ClimbingHallSectionModel, ClimbingWallModel, LineModel, RouteModel)
.select_from(ClimbingHallSectionModel)
.join(ClimbingWallModel)
.join(LineModel)
.join(RouteModel)
.filter(RouteModel.active==True).all())
But this would return a list of tubles:
sections = [
(<ClimbingHallSectionMode 1>, <ClimbingWallModel 1>, <LineModel 1>, <RouteModel 1>),
(<ClimbingHallSectionMode 1>, <ClimbingWallModel >, <LineModel 1>, <RouteModel 2>),...]
I don't know if this is even possible but how can I "pass" the filter arguments to all related tables so that the a section would only contain theses wall which containing lines which contain only routes with active set to true?
Here's a (currently partial) demonstration of the joinedload option.
Change your RouteModel model so that relationships adorn the parent table:
class ClimbingHallSectionModel(BaseModel):
__tablename__ = 'climbing_hall_section'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
climbing_hall_id = db.Column(db.Integer, db.ForeignKey('climbing_hall.id', ondelete='CASCADE'), nullable=False)
walls = db.relationship('ClimbingWallModel', lazy='select', passive_deletes=True)
class ClimbingWallModel(BaseModel):
__tablename__ = 'climbing_wall'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
climbing_hall_section_id = db.Column(db.Integer, db.ForeignKey('climbing_hall_section.id', ondelete='CASCADE'), nullable=False)
height = db.Column(db.Integer)
lines = db.relationship('LineModel', lazy='select', passive_deletes=True)
class LineModel(BaseModel):
__tablename__ = 'line'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
climbing_wall_id = db.Column(db.Integer, db.ForeignKey('climbing_wall.id', ondelete='CASCADE'), nullable=False)
routes = db.relationship('RouteModel', lazy='select', passive_deletes=True)
class RouteModel(BaseModel):
__tablename__ = 'route'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
active = db.Column(db.Boolean, nullable=False, default=True)
line_id = db.Column(db.Integer, db.ForeignKey('line.id', ondelete='CASCADE'), nullable=False)
rev_routes = db.relationship('LineModel', back_populates='routes')
Then, this query ought to give you the related table rows you need, at least for LineModel table:
sections = (db.session.query(RouteModel)
.options(joinedload(RouteModel.rev_routes))
.filter(RouteModel.active==True).all())
For each section in sections, iterate section.rev_routes to access filtered rows in that related table. The joinedload concept does not work (AFAIK) in a sequence of chained relationships, but this may be a start.

Flask SQLAlchemy custom field name for foreign key?

Relationship = Many Heros have one Planet.
I'd like to be able to call hero.home_planet because the Star Wars characters move around in space a lot. But under my model, the field is called planet_id.
Is there a way to set a custom name for a foreign key field?
Like a planet_id = db.Column( name ='home_planet')?
Could I just change the name of the table name to __tablename__ = 'home_planet'?
class Hero(db.Model):
__tablename__ = 'heroes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
race = db.Column(db.String())
planet_id = db.Column(db.Integer, db.ForeignKey('planets.id'), nullable=True)
def __repr__(self):
return '<Hero %r>' % self.name
With flask_sqlalchemy:
class Planet(db.Model)
__tablename__ = 'planet'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
heros = db.relationship('Hero', backref='home_planet')
class Hero(db.Model):
__tablename__ = 'heroes'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
race = db.Column(db.String())
planet_id = db.Column(db.Integer, db.ForeignKey('planets.id'), nullable=True)
def __repr__(self):
return '<Hero %r>' % self.name
Now you can call your_hero.home_planet
It's worth going through the SQLAlchemy docs to learn about the huge number of options you can use here, but it sounds like a simple relationship() will work:
planet = relationship('Planet', backref='heroes')
You can now call my_hero.planet, or the reverse my_planet.heroes

Many to Many - Association Object query based on extra association field

I have a many to many association Object relationship. The association object has an extra field for a timestamp. The idea is to only return the associated relationship if the timestamp of the association is within a specific time frame.
models.py
from rvt import db
from sqlalchemy_utils import IPAddressType
class ReportAssociation(db.Model):
id = db.Column(db.Integer, primary_key=True)
entity_id = db.Column(db.Integer, db.ForeignKey('entity.id'))
report_id = db.Column(db.Integer, db.ForeignKey('report_map.id'))
timestamp = db.Column(db.DateTime)
report_map = db.relationship('ReportMap', back_populates='entities')
entity = db.relationship('Entity', back_populates='report_maps')
__table_args__ = (
db.UniqueConstraint('entity_id', 'report_id', 'timestamp'),
)
def __repr__(self):
return f'ReportAssociation: {self.entity} {self.report_map} - {self.timestamp}'
class Entity(db.Model):
id = db.Column(db.Integer, primary_key=True)
ip_address = db.Column(IPAddressType, unique=True, nullable=False)
hostname = db.Column(db.String(64), index=True, nullable=True)
region = db.Column(db.String(32), index=True, nullable=True)
geo = db.Column(db.String(32), index=True, nullable=True)
city = db.Column(db.String(32), index=True, nullable=True)
asn = db.Column(db.String(8), index=True, nullable=True)
subnet_desc = db.Column(db.String(256), index=True, nullable=True)
report_maps = db.relationship('ReportAssociation', back_populates='entity', lazy="dynamic")
timestamp = db.Column(db.DateTime)
def __repr__(self):
return f'<Entity {self.ip_address}>'
class ReportMap(db.Model):
id = db.Column(db.Integer, primary_key=True)
pattern = db.Column(db.String(64), nullable=False, unique=True)
name = db.Column(db.String(32), nullable=False, unique=True)
short_name = db.Column(db.String(32), nullable=False, unique=True)
wiki_url = db.Column(db.String(128), nullable=True, unique=False)
entities = db.relationship('ReportAssociation', back_populates='report_map', lazy="dynamic")
def __repr__(self):
return f'<ReportMap {self.name}>'
The problem I am having is with the query. The following works.
>>> e = Entity.query.one()
>>> e
<Entity 101.98.10.156>
>>> for i in e.report_maps.all():
... print(i.timestamp)
...
2019-04-02 21:15:49.985126
2019-04-02 21:15:59.028121
>>> for i in e.report_maps.filter_by(timestamp='2019-04-02 21:15:59.028121'):
... print(i.timestamp)
...
2019-04-02 21:15:59.028121
The problem comes in when I try to use something like below
>>> for i in e.report_maps.filter(timestamp < '2019-04-02 21:15:59.028121'):
... print(i.timestamp)
...
Traceback (most recent call last):
File "<console>", line 1, in <module>
NameError: name 'timestamp' is not defined
Any help would be appreciated. Thanks
When you use filter method you must use ClassName.attribue so can use you last query like this:
e.report_maps.filter(Entity.timestamp < '2019-04-02 21:15:59.028121').all()

Flask-appbuilder many-to-may relationship sqlalchemy.exc.NoReferencedTableError Error

I've read through all of these (https://stackoverflow.com/search?q=sqlalchemy.exc.NoReferencedTableError%3A), the Flask-appbuilder docs, the sqlalchemy docs, and the Flask-sqlalchemy docs and more. Unfortunately, I can't find any full examples of a many-to-many sqlalchemy relationship.
I have a python Flask app using flask-appbuilder (which relies on flask-sqlalchemy). My app/model.py file has this:
field_feature_association = Table('field_feature_association',Base.metadata,
Column('field_id', Integer, ForeignKey('field.id')),
Column('feature_id',Integer, ForeignKey('feature.id')),
schema="main"
)
class field(Model):
__tablename__ = 'field'
id = Column(Integer, primary_key=True)
name = Column(String(70), nullable=False)
database_type = Column(String(70)) #varchar(255), text, int
joinable_to = Column(Text())
notes = Column(Text()) #don't use this for X
table_id = Column(Integer, ForeignKey('table.id'))
table = relationship("table")
features = relationship("feature",
secondary = field_feature_association,
backref = backref('fields'),
)
def __repr__(self):
return self.name
class feature(Model):
__tablename__ = 'feature'
id = Column(Integer, primary_key=True)
name = Column(String(70), unique = True, nullable=False)
field_id = Column(Integer, ForeignKey('field.id'))
#field = relationship("field")
def __repr__(self):
return self.name
It's generating this error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'field_feature_association.feature_id' could not find table 'feature' with which to generate a foreign key to target column 'id'
Thoughts on how to fix this error?
Here is a working sample of many-to-many in SQLAlchemy. Moreover I modified your model and it works fine:
field_feature_association = db.Table('field_feature_association', db.Model.metadata,
db.Column('field_id', db.Integer, db.ForeignKey('field.id')),
db.Column('feature_id', db.Integer, db.ForeignKey('feature.id')),
schema="main"
)
class Field(db.Model):
__tablename__ = 'field'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), nullable=False)
database_type = db.Column(db.String(70)) # varchar(255), text, int
joinable_to = db.Column(db.Text())
notes = db.Column(db.Text()) # don't use this for X
features = db.relationship("Feature",
secondary=field_feature_association,
backref=db.backref('fields'),
)
def __repr__(self):
return self.name
class Feature(db.Model):
__tablename__ = 'feature'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True, nullable=False)
field_id = db.Column(db.Integer, db.ForeignKey('field.id'))
# field = relationship("field")
def __repr__(self):
return self.name
and this is how to use it:
field = Field()
field.name="filed1"
feature = Feature()
feature.name = "feature1"
field.features.append(feature)
db.session.add(field)
db.session.commit()
My database object is imported as ’db’ and I have used it explicitely to refer to other types.

Self-Referential Association Relationship SQLalchemy

In my flask application with flask-sqlalchemy i need to create association between two contact
here is my Contact model
class Contact(db.Model):
__tablename__ = 'contact'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(120), nullable=False, unique=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
to_contacts = db.relationship('Contact',
secondary='ContactRelation',
primaryjoin='id==contactrelation.c.from_contact_id',
secondaryjoin='id==contactrelation.c.to_contact_id',
backref='from_contacts')
and my association class ContactRelation:
class ContactRelation(db.Model):
__tablename__ = 'contactrelation'
id = db.Column(db.Integer, primary_key=True)
from_contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
to_contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
relation_type = db.Column(db.String(100), nullable=True)
i have error :
AttributeError: type object 'ContactRelation' has no attribute 'c'
Thanks to Michel and Simon on SQLAlchemy mailing list i need association_proxy and two relation to Contact relation.
class Contact(db.Model):
__tablename__ = 'contact'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(120), nullable=False, unique=False)
created_on = db.Column(db.DateTime, default=datetime.utcnow)
birthday = db.Column(db.DateTime)
background = db.Column(db.Text)
photo = db.Column(db.Unicode(120))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
to_contacts = association_proxy('to_relations', 'to_contact')
from_contacts = association_proxy('from_relations', 'from_contact')
class ContactRelation(db.Model):
__tablename__ = 'contactrelation'
id = db.Column(db.Integer, primary_key=True)
from_contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
to_contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
relation_type = db.Column(db.String(100), nullable=True)
from_contact = db.relationship(Contact,
primaryjoin=(from_contact_id == Contact.id),
backref='to_relations')
to_contact = db.relationship(Contact,
primaryjoin=(to_contact_id == Contact.id),
backref='from_relations')
Self-referential many-to-many relationship with Association Object.
User Class:
class User(Base):
__tablename__ = "User"
id = Column(String(36), primary_key=True, default=lambda : str(uuid1()))
Association Class:
class UserIgnore(Base):
__tablename__ = "UserIgnore"
id = Column(String(36), primary_key=True, default=lambda : str(uuid1()))
ignored_by_id = Column("ignored_by_id", String(36), ForeignKey("User.id"), primary_key=True)
ignored_by = relationship("User", backref="ignored_list", primaryjoin=(User.id == ignored_by_id))
ignored_id = Column("ignored_id", String(36), ForeignKey("User.id"), primary_key=True)
ignored = relationship("User", backref="ignored_by_list", primaryjoin=(User.id == ignored_id))
Access the relationship objects with
someUser.ignored_list
or
someUser.ignored_by_list
Thanks to Sean
Your relationship is not correctly designed. A secondary should be an ordinary table, not a mapped class. If you want the extra data (relation_type) on your ContactRelation, you should use the Association Table pattern described in the SQLAlchemy Relationship docs: http://docs.sqlalchemy.org/en/rel_1_1/orm/basic_relationships.html#association-object
it seems that if you change the to_contacts to something like below, your problem will be solved:
to_contacts = db.relationship('Contact',
secondary='ContactRelation',
primaryjoin='id==contactrelation.from_contact_id',
secondaryjoin='id==contactrelation.to_contact_id',
backref='from_contacts')