Ordering🔗

In this section, we'll cover the everything necessary for adding ordering for your QueryTypes.

OrderSet🔗

An OrderSet is a collection of Order objects that can be applied to a QueryType. In GraphQL, they represent a GraphQL Enum, which when added to a QueryType creates an input argument for ordering the results of a QueryType.

A basic OrderSet is created by subclassing OrderSet and adding its Django Model as a generic type parameter:

1
2
3
4
5
6
7
8
9
from undine import OrderSet, QueryType

from .models import Task


class TaskOrderSet(OrderSet[Task]): ...


class TaskType(QueryType[Task], orderset=TaskOrderSet): ...

Auto-generation🔗

By default, an OrderSet automatically introspects its model and converts the model's fields to input fields on the generated Enum. Given the following models:

1
2
3
4
5
6
7
from django.db import models


class Task(models.Model):
    name = models.CharField(max_length=255)
    done = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

Simply subclassing OrderSet creates the following Enum:

enum TaskOrderSet {
  pkAsc
  pkDesc
  nameAsc
  nameDesc
  doneAsc
  doneDesc
  createdAtAsc
  createdAtDesc
}

This Enum has all of the Task model's fields translated into Enum values, both in ascending and descending directions. With this, you can freely create any ordering you want by combining the different enum values.

query {
  tasks(
    orderBy: [
      pkAsc
      nameDesc
    ]
  ) {
    name
  }
}

This will order the Task objects by their primary key in ascending order, and then by their name in descending order.

You can disable auto-generation by setting the auto argument to False in the class definition:

1
2
3
4
5
6
7
from undine import OrderSet

from .models import Task


# This would create an empty `Enum`, which is not allowed in GraphQL.
class TaskOrderSet(OrderSet[Task], auto=False): ...

Alternatively, you could exclude some Orders from the auto-generation by setting the exclude argument:

1
2
3
4
5
6
from undine import OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task], exclude=["pk"]): ...

Schema name🔗

By default, the name of the generated Enum is the same as the name of the OrderSet class. If you want to change the name, you can do so by setting the schema_name argument:

1
2
3
4
5
6
from undine import OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task], schema_name="TaskOrderingChoices"): ...

Description🔗

You can provide a description using the description argument.

1
2
3
4
5
6
7
from undine import OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    """Description."""

Directives🔗

You can add directives to the OrderSet by providing them using the directives argument.

from graphql import DirectiveLocation

from undine import OrderSet
from undine.directives import Directive

from .models import Task


class MyDirective(Directive, locations=[DirectiveLocation.ENUM]): ...


class TaskOrderSet(OrderSet[Task], directives=[MyDirective()]): ...

See the Directives section for more details on directives.

GraphQL extensions🔗

You can provide custom extensions for the OrderSet by providing a extensions argument with a dictionary containing them. These can then be used however you wish to extend the functionality of the OrderSet.

1
2
3
4
5
6
from undine import OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task], extensions={"foo": "bar"}): ...

OrderSet extensions are made available in the GraphQL Enum extensions after the schema is created. The OrderSet itself is found in the extensions under a key defined by the ORDERSET_EXTENSIONS_KEY setting.

Order🔗

An Order is a class that is used to define a possible ordering for an OrderSet. Usually Orders correspond to fields on the Django model for their respective OrderSet. In GraphQL, it represents an EnumValue in a GraphQL Enum.

An Order always requires a reference which it will use to create the Django OrderBy expression for the Order.

Model field references🔗

As seen in the OrderSet section, you don't need to provide model fields explicitly thanks to auto-generation, but if you wanted to be more explicit, you could add the Orders to the OrderSet class body. In this case, the Order can be used without a reference, as its attribute name in the OrderSet class body can be used to identify the corresponding model field.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order()

To be a bit more explicit, you could use a string referencing the model field:

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order("name")

For better type safety, you can also use the model field itself:

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(Task.name)

Being explicit like this is only required if the name of the attribute in the GraphQL schema is different from the model field name.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    title = Order("name")

Expression references🔗

Django ORM expressions can also be used as the references.

1
2
3
4
5
6
7
8
9
from django.db.models.functions import Reverse

from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(Reverse("name"))

Remember that subqueries are also counted as expressions.

from django.db.models import OuterRef

from undine import Order, OrderSet
from undine.utils.model_utils import SubqueryCount

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    copies = Order(SubqueryCount(Task.objects.filter(name=OuterRef("name"))))

Null placement🔗

If the model field or expression used by the Order is nullable, the null_placement argument can be used to specify the position of null values.

1
2
3
4
5
6
7
8
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(null_placement="first")
    created_at = Order(null_placement="last")

By default, null values are placed according to your database default.

Field name🔗

A field_name can be provided to explicitly set the Django model field name that the Order corresponds to. This can be useful when the field has a different name and type in the GraphQL schema than in the model.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    title = Order(field_name="name")

Schema name🔗

A schema_name can be provided to override the name of the Order in the GraphQL schema. This can be useful for renaming fields for the schema, or when the desired name is a Python keyword and cannot be used as the Order attribute name.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(schema_name="title")

Description🔗

By default, an Order is able to determine its description based on its reference. For example, for a model field, the description is taken from its help_text.

If the reference has no description, or you wish to add a different one, this can be done in two ways:

1) By setting the description argument.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(description="Order by task name.")

2) As class variable docstrings.

1
2
3
4
5
6
7
8
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order()
    """Order by task name."""

Deprecation reason🔗

A deprecation_reason can be provided to mark the Order as deprecated. This is for documentation purposes only, and does not affect the use of the Order.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(deprecation_reason="Use something else.")

Directives🔗

You can add directives to the Order by providing them using the directives argument.

from graphql import DirectiveLocation

from undine import Order, OrderSet
from undine.directives import Directive

from .models import Task


class MyDirective(Directive, locations=[DirectiveLocation.ENUM_VALUE]): ...


class TaskOrderSet(OrderSet[Task]):
    name = Order(directives=[MyDirective()])

See the Directives section for more details on directives.

GraphQL extensions🔗

You can provide custom extensions for the Order by providing a extensions argument with a dictionary containing them. These can then be used however you wish to extend the functionality of the Order.

1
2
3
4
5
6
7
from undine import Order, OrderSet

from .models import Task


class TaskOrderSet(OrderSet[Task]):
    name = Order(extensions={"foo": "bar"})

Order extensions are made available in the GraphQL Enum value extensions after the schema is created. The Order itself is found in the extensions under a key defined by the ORDER_EXTENSIONS_KEY setting.