graphene-sqlalchemy
graphene-sqlalchemy copied to clipboard
Many to Many relationships
There's no documentation on how to configure m2m relationships. DJango uses a special ManyToManyField type. Can anyone provide an example with Graphene-SQLAlchemy?
Can you elaborate on what you mean by "configure many-to-many relationships"? They should be generated out-of-the box.
Does your referenced the type implement the Node
interface?
They do not work out of the box unfortunately.
Take for example the tables user
and keyword
that are connected by a many-to-many relation in user_keyword
:
class CommonMixin(object):
@declared_attr
def __tablename__(cls):
name = cls.__name__
return name.lower()
pid = Column(Integer, primary_key=True)
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.pid'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.pid'), primary_key=True)
class User(Base, CommonMixin):
name = Column(String)
keywords = relationship(
"Keyword",
secondary="user_keyword",
back_populates="users"
)
class Keyword(Base, CommonMixin):
name = Column(String)
users = relationship(
"User",
secondary="user_keyword",
back_populates="keywords"
)
Then add the schema for User
class User(SQLAlchemyObjectType):
class Meta:
model = models.User
interfaces = (relay.Node,)
and the query
all_users = SQLAlchemyConnectionField(User)
Then only these fields are available, i.e., keywords
is missing:
{
allUsers {
edges {
node {
id
pid
name
}
}
}
}
You need to manually set the keywords
field:
class Keyword(SQLAlchemyObjectType):
class Meta:
model = models.Keyword
interfaces = (relay.Node, )
class User(SQLAlchemyObjectType):
class Meta:
model = models.User
interfaces = (relay.Node,)
keywords = graphene.List(Keyword)
Then this will work:
{
allUsers {
edges {
node {
id
pid
name
keywords {
name
}
}
}
}
}
However, that won't work if you also want to add users
to Keyword
I'm trying to solve a similar problem, but (to extend the example) also need to go from Keyword to User. Unfortunately, I end up with a circular reference problem.
class Keyword(SQLAlchemyObjectType):
class Meta:
model = models.Keyword
interfaces = (relay.Node, )
users = graphene.List(User) # <--- error because User is defined later
class User(SQLAlchemyObjectType):
...
I'm trying to find out how to do the equivalent of a late-bind.
To answer my own question, there's two ways to do what I need in Graphene - neither are in the docs, but they're in the code. The first is to use a lambda function, the second is to use a string. For my trivial example above, simply define users like so:
users = graphene.List(lambda: User)
You can also use a string with a dotted reference:
users = graphene.List('mymodule.myfile.User')
Since I prefer to just use the sqlalchemy object reference, I wrote myself a little helper:
from graphene_sqlalchemy.registry import get_global_registry
def get_model_type(model):
registry = get_global_registry()
return lambda: registry.get_type_for_model(model)
class Keyword(SQLAlchemyObjectType):
...
users = graphene.List(get_model_type(models.User))
You need to manually set the
keywords
field:class Keyword(SQLAlchemyObjectType): class Meta: model = models.Keyword interfaces = (relay.Node, ) class User(SQLAlchemyObjectType): class Meta: model = models.User interfaces = (relay.Node,) keywords = graphene.List(Keyword)
Then this will work:
{ allUsers { edges { node { id pid name keywords { name } } } } }
However, that won't work if you also want to add
users
toKeyword
This answer was pretty useful for me. Thanks a lot!