managers.py 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445
  1. from django.db.models import Manager
  2. from django.db.models.expressions import RawSQL
  3. NAT1 = r"CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)"
  4. NAT2 = r"SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')"
  5. NAT3 = r"CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)"
  6. class NaturalOrderingManager(Manager):
  7. """
  8. Order objects naturally by a designated field (defaults to 'name'). Leading and/or trailing digits of values within
  9. this field will be cast as independent integers and sorted accordingly. For example, "Foo2" will be ordered before
  10. "Foo10", even though the digit 1 is normally ordered before the digit 2.
  11. """
  12. natural_order_field = 'name'
  13. def get_queryset(self):
  14. queryset = super().get_queryset()
  15. db_table = self.model._meta.db_table
  16. db_field = self.natural_order_field
  17. # Append the three subfields derived from the designated natural ordering field
  18. queryset = (
  19. queryset.annotate(_nat1=RawSQL(NAT1.format(db_table, db_field), ()))
  20. .annotate(_nat2=RawSQL(NAT2.format(db_table, db_field), ()))
  21. .annotate(_nat3=RawSQL(NAT3.format(db_table, db_field), ()))
  22. )
  23. # Replace any instance of the designated natural ordering field with its three subfields
  24. ordering = []
  25. for field in self.model._meta.ordering:
  26. if field == self.natural_order_field:
  27. ordering.append('_nat1')
  28. ordering.append('_nat2')
  29. ordering.append('_nat3')
  30. else:
  31. ordering.append(field)
  32. # Default to using the _nat indexes if Meta.ordering is empty
  33. if not ordering:
  34. ordering = ('_nat1', '_nat2', '_nat3')
  35. return queryset.order_by(*ordering)