Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set one to many and one to one relationship at same time in Flask-SQLAlchemy?

I'm trying to create one-to-one and one-to-many relationship at the same time in Flask-SQLAlchemy. I want to achieve this:

"A group has many members and one administrator."

Here is what I did:

class Group(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(140), index=True, unique=True)
    description = db.Column(db.Text)
    created_at = db.Column(db.DateTime, server_default=db.func.now())

    members = db.relationship('User', backref='group')
    admin = db.relationship('User', backref='admin_group', uselist=False)

    def __repr__(self):
        return '<Group %r>' % (self.name)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)

    group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
    admin_group_id = db.Column(db.Integer, db.ForeignKey('group.id'))

    created_at = db.Column(db.DateTime, server_default=db.func.now())

However I got an error:

sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Group.members - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.

Does anyone know how to do that properly?

like image 854
Vayn Avatar asked Jul 04 '15 06:07

Vayn


People also ask

What is a one-to-many database relationship in flask?

You will learn how to use SQLite with Flask and how one-to-many database relationships work. A one-to-many database relationship is a relationship between two database tables where a record in one table can reference several records in another table.

What is flask-SQLAlchemy?

Flask-SQLAlchemy is a Flask extension that makes using SQLAlchemy with Flask easier, providing you tools and methods to interact with your database in your Flask applications through SQLAlchemy.

What is a one to many relationship in SQL?

One To Many¶ A one to many relationship places a foreign key on the child table referencing the parent. relationship()is then specified on the parent, as referencing a collection of items represented by the child:

How do I create a database schema in flask?

Once your programming environment is activated, install Flask using the following command: Once the installation is complete, you can now create the database schema file that contains SQL commands to create the tables you need to store your to-do data.


2 Answers

The problem you're getting comes from the fact that you've defined two links between your classes - a User has a group_id (which is a Foreign Key), and a Group has an admin (which is also defined by a Foreign Key). If you remove the Foreign Key from the admin field the connection is no longer ambiguous and the relationship works. This is my solution to your problem (making the link one-to-one):

from app import db,app

class Group(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(140), index=True, unique=True)
    description = db.Column(db.Text)
    created_at = db.Column(db.DateTime, server_default=db.func.now())
    admin_id = db.Column(db.Integer) #, db.ForeignKey('user.id'))
    members = db.relationship('User', backref='group')

    def admin(self):
        return User.query.filter_by(id=self.admin_id).first()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True)
    group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
    created_at = db.Column(db.DateTime, server_default=db.func.now())

The one drawback to this is that the group object doesn't have a neat admin member object you can just use - you have to call the function group.admin() to retrieve the administrator. However, the group can have many members, but only one of them can be the administrator. Obviously there is no DB-level checking to ensure that the administrator is actually a member of the group, but you could add that check into a setter function - perhaps something like:

# setter method
def admin(self, user):
    if user.group_id == self.id:
        self.admin_id = user.id

# getter method
def admin(self):
    return User.query.filter_by(id=self.admin_id).first()
like image 184
Aaron D Avatar answered Sep 29 '22 07:09

Aaron D


The solution is to specify the foreign_keys argument on all relationships:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)

    group_id = Column(Integer, ForeignKey('groups.id'))
    admin_group_id = Column(Integer, ForeignKey('groups.id'))

class Group(Base):
    __tablename__ = 'groups'
    id = Column(Integer, primary_key=True)

    members = relationship('User', backref='group', foreign_keys=[User.group_id])
    admin = relationship('User', backref='admin_group', uselist=False, foreign_keys=[User.admin_group_id])

Perhaps consider the admin relation in the other direction to implement "a group has many members and one admin":

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)

    group_id = Column(Integer, ForeignKey('groups.id'))
    group = relationship('Group', foreign_keys=[group_id], back_populates='members')


class Group(Base):
    __tablename__ = 'groups'
    id = Column(Integer, primary_key=True)

    members = relationship('User', foreign_keys=[User.group_id], back_populates='group')

    admin_user_id = Column(Integer, ForeignKey('users.id'))
    admin = relationship('User', foreign_keys=[admin_user_id], post_update=True)

See note on post_update in the documentation. It is necessary when two models are mutually dependent, referencing each other.

like image 25
jeinarsson Avatar answered Sep 29 '22 09:09

jeinarsson