Reputation: 17
I am trying to make a table of games and each game should point to two players or users.
This is the code I have right now. I am using SQLAlchemy
UserGame = db.Table('UserGame',
db.Column('game_id',db.Integer,db.ForeignKey('game.id')),
db.Column('user_id',db.Integer,db.ForeignKey('user.id')),
)
class User(db.Model):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
games = db.relationship('Game', secondary=UserGame, backref='players')
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User %r>' % self.username
class Game(db.Model):
__tablename__ = 'Game'
id = db.Column(db.Integer, primary_key=True)
player1 = db.Column(db.Integer,db.ForeignKey('User.id')
player2 = db.Column(db.Integer,db.ForeignKey('User.id')
ended = db.Column(db.Boolean)
winner_username = db.Column(db.String(80),db.ForeignKey('User.username'))
However, when I try to create a user, I get the following error:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship User.games - there are no foreign keys linking these tables via secondary table 'UserGame'. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' and 'secondaryjoin' expressions.
Any ideas?
Upvotes: 0
Views: 927
Reputation: 44092
As soon as you find M:N relationship, you are about to use so called association class describing this relationship.
In your case, you have relationship between two players and the meaning of the association is "a game they play together".
Minimal association class describes:
There might be ternary or even more complex associations, but these are rather rare.
In some cases, and this is the case of Game here, we might add some additional data to that association (like result, who was the winner etc.).
There is no conceptual problem in describing association between two instances of the same class.
There are three methods for modelling association classes in sqlalchemy
In sqlalchemy, simple M:N relationship is often modelled by so called secondary table. Unfortunately, this is not the case here as we need to attach additional information to known associations.
This is the way to go in your case. Code will follow.
There are some advanced techniques, using association_proxy, which might hide the association class instance, but we do not need this (in fact, we want to see the concept of the game in the model).
Following example models teams, which might play together a game. Each game is played at host and the other team is considered a guest. We record with each game a score.
This example shall be easy to modify for your scenario.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Team(Base):
__tablename__ = "team"
id = Column(Integer, primary_key=True)
name = Column(String(40))
def __init__(self, name):
self.name = name
def __repr__(self):
return "<Team: {self.name}>".format(self=self)
class Game(Base):
__tablename__ = "game"
host_id = Column(Integer, ForeignKey("team.id"), primary_key=True)
guest_id = Column(Integer, ForeignKey("team.id"), primary_key=True)
host_score = Column(Integer)
guest_score = Column(Integer)
def __init__(self, host, guest, host_score, guest_score):
self.host = host
self.guest = guest
self.host_score = host_score
self.guest_score = guest_score
host = relationship(Team, lazy="joined", foreign_keys="Game.host_id", backref="games_ashost")
guest = relationship(Team, lazy="joined", foreign_keys="Game.guest_id", backref="games_asguest")
def __repr__(self):
templ = "<Game: {self.host.name} vs {self.guest.name} score: {self.host_score}:{self.guest_score}>"
return templ.format(self=self)
Apart from setting up the class Game
, it is important to defile host
and guest
meatinformation for this class. We use relationship
for this purpose.
As we are associating two instances of the same class, sqlalchemy has difficulties to find what class attributes to link with host
and which with guest
. For this purpose, the relationship
must use foreign_keys
, which might go in form of an array or a string of foreign keys to use. (this was for me the most difficult part as examples do not show such example).
backref
in relationship
adds new attribute to partnering class, so backref="games_ashost
will add for each Team
an attribute Team().games_ashost
.
Import what is needed
>>> from model import *
>>> from sqlalchemy import *
>>> from sqlalchemy.orm import Session
Prepare engine and session
>>> engine = create_engine("sqlite://")
>>> Base.metadata.create_all(engine)
>>> session = Session(engine)
Create teams alfa, beta, gama, delta
>>> alfa = Team("alfa")
>>> beta = Team("beta")
>>> gama = Team("gama")
>>> delta = Team("delta")
>>> alfa
<Team: alfa>
>>> beta
<Team: beta>
>>> gama
<Team: gama>
>>> delta
<Team: delta>
Store them in database
>>> session.add_all([alfa, beta, gama, delta])
>>> session.commit()
Instantiate games (getween Alfa and Beta and Alfa and Gama) and store them in the database
>>> game_ab = Game(alfa, beta, 0, 0)
>>> game_ab
<Game: alfa vs beta score: 0:0>
>>> game_ag = Game(alfa, gama, 0, 0)
>>> session.add_all([game_ab, game_ag])
>>> session.commit()
Explore available properties of teams
>>> alfa.games_ashost
[<Game: alfa vs beta score: 0:0>, <Game: alfa vs gama score: 0:0>]
>>> alfa.games_asguest
[]
>>> beta.games_ashost
[]
>>> beta.games_asguest
[<Game: alfa vs beta score: 0:0>]
>>> gama.games_ashost
[]
>>> gama.games_asguest
[<Game: alfa vs gama score: 0:0>]
>>> gama
<Team: gama>
Explore properties of games
>>> game_ab.host
<Team: alfa>
>>> game_ab.guest
<Team: beta>
Upvotes: 2