|
@@ -4,29 +4,35 @@ from django.db.models import Manager
|
|
|
|
|
|
|
|
|
|
|
|
|
class NaturalOrderByManager(Manager):
|
|
class NaturalOrderByManager(Manager):
|
|
|
|
|
+ """
|
|
|
|
|
+ Order objects naturally by a designated field. 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.
|
|
|
|
|
+ """
|
|
|
|
|
+ natural_order_field = None
|
|
|
|
|
|
|
|
- def natural_order_by(self, *fields):
|
|
|
|
|
- """
|
|
|
|
|
- Attempt to order records naturally by segmenting a field into three parts:
|
|
|
|
|
|
|
+ def get_queryset(self):
|
|
|
|
|
|
|
|
- 1. Leading integer (if any)
|
|
|
|
|
- 2. Middle portion
|
|
|
|
|
- 3. Trailing integer (if any)
|
|
|
|
|
|
|
+ queryset = super(NaturalOrderByManager, self).get_queryset()
|
|
|
|
|
|
|
|
- :param fields: The fields on which to order the queryset. The last field in the list will be ordered naturally.
|
|
|
|
|
- """
|
|
|
|
|
db_table = self.model._meta.db_table
|
|
db_table = self.model._meta.db_table
|
|
|
- primary_field = fields[-1]
|
|
|
|
|
|
|
+ db_field = self.natural_order_field
|
|
|
|
|
|
|
|
- id1 = '_{}_{}1'.format(db_table, primary_field)
|
|
|
|
|
- id2 = '_{}_{}2'.format(db_table, primary_field)
|
|
|
|
|
- id3 = '_{}_{}3'.format(db_table, primary_field)
|
|
|
|
|
-
|
|
|
|
|
- queryset = super(NaturalOrderByManager, self).get_queryset().extra(select={
|
|
|
|
|
- id1: "CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, primary_field),
|
|
|
|
|
- id2: "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, primary_field),
|
|
|
|
|
- id3: "CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, primary_field),
|
|
|
|
|
|
|
+ # Append the three subfields derived from the designated natural ordering field
|
|
|
|
|
+ queryset = queryset.extra(select={
|
|
|
|
|
+ '_nat1': "CAST(SUBSTRING({}.{} FROM '^(\d{{1,9}})') AS integer)".format(db_table, db_field),
|
|
|
|
|
+ '_nat2': "SUBSTRING({}.{} FROM '^\d*(.*?)\d*$')".format(db_table, db_field),
|
|
|
|
|
+ '_nat3': "CAST(SUBSTRING({}.{} FROM '(\d{{1,9}})$') AS integer)".format(db_table, db_field),
|
|
|
})
|
|
})
|
|
|
- ordering = fields[0:-1] + (id1, id2, id3)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 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)
|
|
|
|
|
|
|
|
return queryset.order_by(*ordering)
|
|
return queryset.order_by(*ordering)
|