Skip to content

Creating complex "where" filters #231

Closed
@maquino1985

Description

@maquino1985

I was trying to create a way to implement filters that could create complex where clauses with groupings and I was beginning to think it was not possible to create a recursive filter when I accidentally made it work. So I just wanted to share this code in case anyone else finds it helpful:

I added a where filter to a subclassed UnsortedSQLAlchemyConnectionField

class SQLAlchemyFilteredConnectionField(UnsortedSQLAlchemyConnectionField):
    def __init__(self, type_, *args, **kwargs):
        model = type_._meta.model
        kwargs.setdefault("where", create_filter_argument(model))
        super(SQLAlchemyFilteredConnectionField, self).__init__(type_, *args, **kwargs)

the magic happens in create_filter_argument:

def create_filter_field(column):
    graphene_type = convert_sqlalchemy_type(column.type, column)
    if graphene_type.__class__ == Field:
        return None

    name = "{}Filter".format(str(graphene_type.__class__))
    if name in field_cache:
        return Field(field_cache[name])

    fields = OrderedDict((key, Field(graphene_type.__class__))
                         for key in ["equal", "notEqual", "lessThan", "greaterThan", "like"])
    fields['in'] = Field(List(graphene_type.__class__))
    field_class: InputObjectType = type(name, (FilterField, InputObjectType), {})
    field_class._meta.fields.update(fields)

    field_cache[name] = field_class
    return Field(field_class)

def create_filter_argument(cls):
    name = "{}Filter".format(cls.__name__)
    if name in argument_cache:
        return Argument(argument_cache[name])
    import re

    NAME_PATTERN = r"^[_a-zA-Z][_a-zA-Z0-9]*$"
    COMPILED_NAME_PATTERN = re.compile(NAME_PATTERN)

    fields = OrderedDict((column.name, field)
                         for column, field in [(column, create_filter_field(column))
                                               for column in inspect(cls).columns.values()] if field and COMPILED_NAME_PATTERN.match(column.name))
    argument_class: InputObjectType = type(name, (FilterArgument, InputObjectType), {})
    argument_class._meta.fields.update(fields)

    nested_argument_class: InputObjectType = copy.deepcopy(argument_class) # not sure if necessary
    argument_class._meta.fields['or'] = Argument(nested_argument_class)
    argument_class._meta.fields['and'] = Argument(nested_argument_class)
    argument_cache[name] = argument_class

    return Argument(argument_class)

This might not be the best or the prettiest way to achieve this, but it allows you to then create filters that refer to themselves recursively so now you can make a graphene request that looks like this

{
  allRequests(where: {visibleId: {equal: 1}, or: {or: {or: {}}, and: {or: {and: {or: {}}}}}}) {
    edges {
      node {
        id
        label
      }
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions