strawberry-graphql / strawberry-django

Strawberry GraphQL Django extension
MIT License
391 stars 115 forks source link

Custom ordering methods doesn't work when value are passed by variables #530

Open Tomschuh opened 1 month ago

Tomschuh commented 1 month ago

When values are passed not in the query but using variables, then strawberry-django isn't able to resolve the ordering sequence. It probably happens only if you create a custom order method using @strawberry_django.order_field annotation.

Example

Query example:

query myQuery($order: ColourOrder) {
    colors( order: $order) {
        id
    }
}   

variables example:

{
    "order": {
        "name": "ASC"
    }
}

Causes

The result is that the query fails with the following assert:

app_1  |   File "/opt/venv/lib/python3.10/site-packages/strawberry_django/ordering.py", line 125, in process_order
app_1  |     res = f.base_resolver(
app_1  |   File "/opt/venv/lib/python3.10/site-packages/strawberry_django/fields/filter_order.py", line 130, in __call__
app_1  |     assert sequence is not None

My humble solution

I am not saying that my solution is right, but maybe it will help someone to get on the right track. I extended the original method a little bit and used it in the custom order method:

def resolve_ordering_sequence(info: Info):
    sequence: dict = {}
    if info is not None and info._raw_info.field_nodes:
        field_node = info._raw_info.field_nodes[0]
        for arg in field_node.arguments:
            if arg.name.value != ORDER_ARG or (
                not isinstance(arg.value, ObjectValueNode)
                and not isinstance(arg.value, VariableNode)
            ):
                continue

            def parse_and_fill(
                field: ObjectValueNode | VariableNode,
                seq: dict[str, OrderSequence],
            ):
                if isinstance(field, VariableNode):
                    order_variable = info.variable_values.get(field.name.value)
                    for i, n in enumerate(order_variable):
                        seq[n] = OrderSequence(seq=i, children={})
                    return

                for i, f in enumerate(field.fields):
                    f_sequence: dict[str, OrderSequence] = {}
                    if isinstance(f.value, ObjectValueNode):
                        parse_and_fill(f.value, f_sequence)

                    seq[f.name.value] = OrderSequence(
                        seq=i, children=f_sequence
                    )

            parse_and_fill(arg.value, sequence)

    return sequence

then in order class:

    @strawberry_django.order_field
    def order(
        self,
        info: Info,
        queryset: QuerySet,
        prefix: str,
        sequence: dict[str, strawberry_django.Ordering] | None,
    ) -> tuple[QuerySet, list[str]]:
        sequence = resolve_ordering_sequence(info)
        return strawberry_django.process_order(
            self,
            info=info,
            queryset=queryset,
            sequence=sequence,
            prefix=prefix,
            skip_object_order_method=True,
        )

System Information

strawberry-graphql==0.227.2 strawberry-graphql-django~=0.39.2

Additional Context

If it doesn't make sense, feel free to delete the issue.

Upvote & Fund

Fund with Polar

bellini666 commented 1 month ago

Hey @Tomschuh ,

I just tried reproducing the issue here and wasn't able to.

Could you provide a reproducible example?

Also, if when providing a reproducible example you find a way to solve it, like in your suggestion, feel free to open a PR for that :)