art1415926535 / graphene-sqlalchemy-filter

Filters for Graphene SQLAlchemy integration
https://pypi.org/project/graphene-sqlalchemy-filter/
MIT License
118 stars 34 forks source link

Is it possible to filter in both directions? #3

Closed AndrewOwenMartin closed 5 years ago

AndrewOwenMartin commented 5 years ago

Is it possible to do something like the following, which allows filtering of addresses by user and filtering of users by their addresses. This code doesn't work because at the time the line address = AddressFilter() is executed, the class AddressFilter hasn't been defined.

class UserFilter(graphene_sqlalchemy_filter.FilterSet):

    address = AddressFilter()

    @classmethod
    def address_filter(cls, info, query, value:

        return cls.address.filter(info, query, value).join(db.Address), None

    class Meta:

        model = db.User
        fields = {"text": [...], "strong": ["eq", "ne"]}
class AddressFilter(graphene_sqlalchemy_filter.FilterSet):

    user = UserFilter()

    @classmethod
    def user_filter(cls, info, query, value):

        return cls.user.filter(info, query, value).join(db.User), None

    class Meta:

        model = db.Address
        fields = {"text": [...], "strong": [...],}

Maybe this is impossible because it would cause an infinite loop somewhere.

I've also tried adding UserFilter.address = AddressFilter() at the bottom of the file, but although it doesn't throw any errors, it also doesn't work.

art1415926535 commented 5 years ago

You have models User, Address, UserAddress?

Filter may be like this

class AddressFilter(graphene_sqlalchemy_filter.FilterSet):
    has_residents = graphene.InputField(lambda: UserFilter)

    class Meta:
        model = db.Address
        fields = {"text": [...], "strong": [...]}

    @classmethod
    def has_residents_filter(cls, info, query, filters):
        sub_query = db_session.query(db.User.id)
        filtered_sub_query = UserFilter.filter(info, sub_query, filters)

        # first arg is a query (graphene-sqlalchemy-filter>=1.7.0)
        residents = cls.aliased(query, db.UserAddress, name='has_residents')  
        query = query.join(
            residents,
            and_(
                db.Address.id == residents.address_id,
                residents.user_id.in_(filtered_sub_query),
            ),
        )

        return query, residents.id.isnot(None)

UserFilter can be written by analogy.

AndrewOwenMartin commented 5 years ago

Thank you, this will be helpful when I need to filter two ways through a join table. E.g. User, Address, UserAddress.

In my case, however, I just had Address and User with a simple one to many relation. One User can have many (email) Addresses, and each (email) Address belongs to exactly one user.

Your response is helpful to my case as it helps me see that I don't need to do filtering in both ways. I.e Filtering (email) Addresses by attributes of the User makes sense, but filtering Users by an attribute on their list of (email) Addresses doesn't make sense. If I do need to filter Users by attributes of their list of (email) Addresses then I can implement a filter function as you have described.

art1415926535 commented 5 years ago

If you need to get one user by email. I think in your case more GraphQL way to write another field, like this

{
  userByEmail(email: "admin@example.com") {id username}
}

or If you have a list of emails:


class UserFilter(FilterSet):
    email_in = graphene.List(graphene.String)

    @classmethod
    def email_in_filter(cls, info, query, emails):
        query = query.join(Address)
        return query, Address.email.in_(emails)
  allUsers(filters: {emailIn: ["a@example.com", "b@example.com"]}) {
    edges { node { id username } }
  }

If you want to get all addresses by user id:

class AddressFilter(FilterSet):
    class Meta:
        model = db.Address
        fields = {"user_id": ['eq']}
{
  allAddresses(filters: {userId: 1}) {
    edges { node { id email } }
  }
}