Erwan
Erwan

Reputation: 1073

Flask-SQLalchemy - Get a relationship argument from a Parent class in a Sub class

Using SQLalchemy and Flask-SQLAlchemy, I'm able to set up the following User class and sub classes Customer and Provider :

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=False)
    user_type = Column(String(32), nullable=False)

    __mapper_args__ = {
    'polymorphic_on':'user_type',
    'polymorphic_identity':'user_type'
    }


class Customer(User):
    __tablename__ = "customer"
    id = Column(Integer, ForeignKey('user.id'), primary_key=True)
    note_customer = Column(Integer)

    __mapper_args__ = {
    'polymorphic_identity':'customer'
    }

class Provider(User):
    __tablename__ = "provider"
    id = Column(Integer, ForeignKey('user.id'), primary_key=True)
    note_provider = Column(Integer)

    __mapper_args__ = {
    'polymorphic_identity':'provider'
    }


class Address(Base):
    __tablename__ = "address"
    id = Column(Integer, primary_key=True)
    number = Column(Integer)
    street = Column(String(100))
    user_id = Column(Integer, ForeignKey('user.id'))
    user = relationship(User)

Then I am able to create a new customer :

u = Customer(name= 'Mark', user_type='customer', note_customer = 15)
db.session.add(u)
db.session.commit()

But I would like to add an address value to my Customer Mark during his creation and I don't succeed in and can't figure out how doing it. I guess I'm wrong with my relationship : for me it's a "one-to-one" type, but many solutions I tried give me errors telling that the address argument is not available for a Customer object type.

Thanks!

Upvotes: 1

Views: 1121

Answers (2)

Ilja Everilä
Ilja Everilä

Reputation: 52929

The problem is that you neither use a backref or explicitly create 2 back populating relationships. Because of this the User model class has no idea of the relationship and no Address instance can be passed to it or subclasses when constructing. The error you're receiving should be:

TypeError: 'address' is an invalid keyword argument for Customer

Using backref argument of relationship

from sqlalchemy.orm import backref

class Address(Base):
    __tablename__ = "address"
    id = Column(Integer, primary_key=True)
    number = Column(Integer)
    street = Column(String(100))
    user_id = Column(Integer, ForeignKey('user.id'))
    user = relationship(User, backref=backref('address', uselist=False))

This creates a relationship attribute address on User model as if you'd added it manually:

    address = relationship('Address', uselist=False, ...)

You need to use a backref() object in order to pass uselist=False since you want a one to one relationship.

Using explicit relationship attributes

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=False)
    user_type = Column(String(32), nullable=False)

    address = relationship('Address', uselist=False, back_populates='user')

class Address(Base):
    __tablename__ = "address"
    id = Column(Integer, primary_key=True)
    number = Column(Integer)
    street = Column(String(100))
    user_id = Column(Integer, ForeignKey('user.id'))
    user = relationship(User, back_populates='address')

The back_populates parameters will establish event listeners that will mirror attribute operations between the classes.

With a proper relationship configuration in place creating a new Customer with an address can be done like this:

u = Customer(name='Mark', user_type='customer', note_customer=15,
             address=Address(number=221, street='Baker Street'))
session.add(u)
session.commit()

or in separate steps:

u = Customer(name='Mark', user_type='customer', note_customer=15)
u.address = Address(number=221, street='Baker Street')
session.add(u)
session.commit()

Upvotes: 2

coralvanda
coralvanda

Reputation: 6596

The error is because your customer class isn't defined to have an address.

You could simply add another few lines to put Mark's address in, and tie the relationship to Mark.

mark_address = Address(number='123', street='Main St', user = u)
db.session.add(mark_address)
db.session.commit()

Upvotes: 0

Related Questions