graphene-sqlalchemy icon indicating copy to clipboard operation
graphene-sqlalchemy copied to clipboard

Support for associationproxy

Open rafmagns-skepa-dreag opened this issue 7 years ago • 6 comments

Hi,

I'm using graphene-sqlalchemy 2.0.0. I haven't found any documentation around association proxy support and my preliminary efforts into finding it in the code/docs/issues have proved futile. Are association proxies supported? If not, is there a plan to support them?

Thanks!

rafmagns-skepa-dreag avatar Jun 15 '18 22:06 rafmagns-skepa-dreag

Seconding this, an initial google search came up dry and I'm wondering how to access related models via the association proxy.

mekhami avatar Jun 28 '18 20:06 mekhami

This is important to me as well. Feels like a core feature that should be present.

oj-lappi avatar Oct 31 '18 19:10 oj-lappi

Does anyone have a workaround for accessing AssociationProxy fields?

Edit: I gave AssociationProxy models their own nodes and linked the associated objects via one to one relationships.

ghost avatar Jan 10 '19 00:01 ghost

Myself got at least part of it to work. I have Playlists and Media models, both many-to-many in both directions (one playlist can have many media, one media can be part of many playlists)

class Playlist(Base):
    __tablename__ = 'playlists'

    id = Column(BigInteger, primary_key=True)
    #other fields here    

    media = association_proxy('media_associations', 'media')
class Media(Base):
    __tablename__ = 'media'

    id = Column(BigInteger, primary_key=True)
    uid = Column(String(50), unique=True, index=True, nullable=False)

    #other fields here    

    playlists = association_proxy('playlist_associations', 'playlist')

These two models are tied together via the association_proxy and

class PlaylistsMedia(Base):
    __tablename__ = 'playlists_media'
    id = Column(BigInteger, primary_key=True)

    playlist_id = Column(BigInteger, ForeignKey('playlists.id'))
    media_uid = Column(String, ForeignKey('media.uid'))

    playlist = relationship(
        'Playlist',
        backref=backref("media_associations", cascade="all, delete-orphan"),
        lazy='subquery'
    )
    media = relationship(
        'Media',
        backref=backref("playlist_associations", cascade="all, delete-orphan"),
        lazy='subquery'
    )

    def __init__(self, playlist=None, media=None):
        self.playlist = playlist
        self.media = media

More details here, my implementation allows to have multiple "media" in one playlists.

On the Graph side

class PlaylistNode(SQLAlchemyObjectType):
    class Meta:
        model = Playlist
        interfaces = (graphene.relay.Node, )

    uid = graphene.String(description='Playlist uid generated at creation')
    name = graphene.String(description='Playlist name as set by user')
    # other fields here

    media = graphene.List(MediaNode, description='collection of media associated to this playlist')

That's the Node for my Playlists model. Notice media, that will allow access to the associated media field I set in the model.

class Query(graphene.ObjectType):
    """Query objects for GraphQL API."""
    class Meta:
        interfaces = (graphene.relay.Node,)
    node = graphene.relay.Node.Field()
    viewer = graphene.Field(Viewer)

    paginate_playlists = graphene.Field(lambda: graphene.List(PlaylistNode),
                                      page=graphene.Int(required=True),
                                      per_page=graphene.Int(required=True),
                                      order_by=graphene.Int(required=True),
                                      )
    @staticmethod
    def resolve_paginate_screens(root, info, **kwargs):
        # here I query the Playlists model and return the items

Accessing that query from frontend with

query paginatePlaylists($page:Int!, $perPage: Int!, $orderBy: Int!){
  paginatePlaylists(page:$page, perPage:$perPage, orderBy:$orderBy){
    uid
    name
    status
    dateCreated
    scheduled
    scheduleStart
    scheduleStop
    onPage
    pagesCount
    media{
      id
      uid
      anyOtherFieldFromTheMediaNode
    }
  }
}

With all that I am able to query media associated to this playlist.

It would be nice to do the same the other way around but I can't add the PlaylistNode as a graphene.List to my MediaNode because scope logic:

  • to add MediaNode in PlaylistNode I first need to create MediaNode and then PlaylistNode. Media can be added to Playlist but Playlist can't be added to Media (it was not yet defined). In SQLAlchemy I can use model names as string and not be limited by the fact that the Class was not yet defined
users_screens_association = Table('users_screens', Base.metadata,
    Column('user_id', BigInteger, ForeignKey('users.id')),
    Column('screen_id', String, ForeignKey('screens.device_id'))
)

mirceaciu avatar Nov 21 '19 16:11 mirceaciu

Does anyone have a workaround for accessing AssociationProxy fields?

Edit: I gave AssociationProxy models their own nodes and linked the associated objects via one to one relationships.

ghost I just understood what you suggested there.

I can create as many nodes as I need from my Media and Playlist models. I can define and use them anywhere I need, and that's what I did and now I have a working Graph for my bidirectional many-to-many association-proxy relationship :D

mirceaciu avatar Nov 21 '19 20:11 mirceaciu

#267 should solve this issue and will be merged soon!

erikwrede avatar May 14 '22 16:05 erikwrede

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related topics referencing this issue.

github-actions[bot] avatar Apr 04 '24 00:04 github-actions[bot]