At s2k digital, we have various django models that have a field for state tracking. This could be a simple field with choices set, or when using django-fsm a so called FSMField, which is based on a CharField. An example models.py
could look like this:
from django.db import models
from django_fsm import FSMField
class BlogPost(models.Model):
STATE_CHOICES = (
("new", "new"),
("clearance", "in clearance"),
("correction", "correction requested"),
("done", "done"),
)
# Fields
id = models.AutoField(primary_key=True)
state = models.FSM(default="new", choices=STATE_CHOICES, blank=False, null=False)
created_at = models.DateTimeField(auto_now_add=True)
# some more very important fields
class Meta:
ordering = ["created_at"]
Most of the time, in addition to tracking state, these fields are also used for sorting. However, not always is it the lexicographic order of the fields’ values that should be used as the basis for sorting, but rather some custom order based on those values.
For example, in the above example, we would like to have the following order:
- correction
- clearance
- new
- done
Lets extend our models.py
to reflect the desired ordering of states and add a custom manager BlogPostManager
to our model:
class BlogPost(models.Model):
STATE_CHOICES = (
("new", "new"),
("clearance", "in clearance"),
("correction", "correction requested"),
("done", "done"),
)
STATE_ORDERING = {
"correction": 1,
"clearance": 2,
"new": 3,
"done": 4,
}
...
...
objects = BlogPostManager()
class Meta:
# We want our BlogPosts to be ordered primarily according to
# STATE_ORDERING, and secondarily by created_at
ordering = ("created_at",)
In our custom BlogPostManager
, we will then override the get_queryset
method to return a queryset that is ordered according to STATE_ORDERING
. We therefore have to annotate our queryset such that the included objects have an IntegerField
called state_order
, which is then used for ordering / sorting the queryset accordingly.
We can achieve all this by utilizing When
and Case
which are both provided by django.db.models
. An example managers.py
could look like this:
from django.db import models
class BlogPostManager(models.Manager):
def get_queryset(self):
qs = super().get_queryset()
if qs.exists():
state_ordering_pairs = self.model.STATE_ORDERING.items()
whens = [
models.When(schein__state=k, then=v) for k, v in state_ordering_pairs
]
# annotate queryset with state_order
qs = qs.annotate(
state_order=models.Case(
*whens, default=9999, output_field=models.IntegerField()
)
)
# Make sure we apply the ordering specified in BlogPost's Meta class
# after ordering by state_order
ordering = ("state_order",) + self.model._meta.ordering
qs = qs.order_by(*ordering)
return qs
This will give us the desired ordering of our BlogPost objects. If you want to learn more about When
and Case
, you can read more about them in the Django documentation.
_
If you are looking for a Django related job, have feedback or questions, feel free to reach out to us on Twitter, LinkedIn or shoot us an email.