2016-07-20 10:07:32 -04:00
|
|
|
from django.db.models import Manager
|
2019-09-29 01:15:46 -04:00
|
|
|
from django.db.models.expressions import RawSQL
|
2016-07-20 10:07:32 -04:00
|
|
|
|
2018-11-06 14:05:23 -05:00
|
|
|
NAT1 = r"CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)"
|
|
|
|
NAT2 = r"SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')"
|
|
|
|
NAT3 = r"CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)"
|
2016-07-20 10:07:32 -04:00
|
|
|
|
2018-11-06 14:05:23 -05:00
|
|
|
|
|
|
|
class NaturalOrderingManager(Manager):
|
2018-06-11 15:10:31 -04:00
|
|
|
"""
|
2018-11-06 14:05:23 -05:00
|
|
|
Order objects naturally by a designated field (defaults to 'name'). Leading and/or trailing digits of values within
|
|
|
|
this field will be cast as independent integers and sorted accordingly. For example, "Foo2" will be ordered before
|
|
|
|
"Foo10", even though the digit 1 is normally ordered before the digit 2.
|
2018-06-11 15:10:31 -04:00
|
|
|
"""
|
2018-11-06 14:05:23 -05:00
|
|
|
natural_order_field = 'name'
|
2016-07-20 10:07:32 -04:00
|
|
|
|
2018-06-11 15:10:31 -04:00
|
|
|
def get_queryset(self):
|
2016-07-20 10:07:32 -04:00
|
|
|
|
2018-11-27 10:52:24 -05:00
|
|
|
queryset = super().get_queryset()
|
2016-07-20 10:07:32 -04:00
|
|
|
|
|
|
|
db_table = self.model._meta.db_table
|
2018-06-11 15:10:31 -04:00
|
|
|
db_field = self.natural_order_field
|
2016-07-20 10:07:32 -04:00
|
|
|
|
2018-06-11 15:10:31 -04:00
|
|
|
# Append the three subfields derived from the designated natural ordering field
|
2019-09-29 01:15:46 -04:00
|
|
|
queryset = (
|
|
|
|
queryset.annotate(_nat1=RawSQL(NAT1.format(db_table, db_field), ()))
|
|
|
|
.annotate(_nat2=RawSQL(NAT2.format(db_table, db_field), ()))
|
|
|
|
.annotate(_nat3=RawSQL(NAT3.format(db_table, db_field), ()))
|
|
|
|
)
|
2018-06-11 15:10:31 -04:00
|
|
|
|
|
|
|
# Replace any instance of the designated natural ordering field with its three subfields
|
|
|
|
ordering = []
|
|
|
|
for field in self.model._meta.ordering:
|
|
|
|
if field == self.natural_order_field:
|
|
|
|
ordering.append('_nat1')
|
|
|
|
ordering.append('_nat2')
|
|
|
|
ordering.append('_nat3')
|
|
|
|
else:
|
|
|
|
ordering.append(field)
|
2016-07-20 10:07:32 -04:00
|
|
|
|
2019-04-30 13:25:37 -04:00
|
|
|
# Default to using the _nat indexes if Meta.ordering is empty
|
|
|
|
if not ordering:
|
|
|
|
ordering = ('_nat1', '_nat2', '_nat3')
|
|
|
|
|
2016-07-20 10:07:32 -04:00
|
|
|
return queryset.order_by(*ordering)
|