diff --git a/binder/views.py b/binder/views.py index 66f355da..54d58937 100644 --- a/binder/views.py +++ b/binder/views.py @@ -21,7 +21,7 @@ from django.http.request import RawPostDataException from django.http.multipartparser import MultiPartParser from django.db import models, connections -from django.db.models import Q, F, Count +from django.db.models import Q, F, Count, Case, When from django.db.models.lookups import Transform from django.utils import timezone from django.db import transaction @@ -1569,8 +1569,7 @@ def _after_expr(self, request, after_id, include_annotations): raise BinderRequestError(f'invalid value for after_id: {after_id!r}') # Now we will build up a comparison expr based on the order by - left_exprs = [] - right_exprs = [] + whens = [] for field in ordering: # First we have to split of a leading '-' as indicating reverse @@ -1578,30 +1577,35 @@ def _after_expr(self, request, after_id, include_annotations): if reverse: field = field[1:] - # Then we build 2 exprs for the left hand side (objs in the query) - # and the right hand side (the object with the provided after id) - left_expr = F(field) + # Then we determine if nulls come last + if field.endswith('__nulls_last'): + field = field[:-12] + nulls_last = True + elif field.endswith('__nulls_first'): + field = field[:-13] + nulls_last = False + else: + nulls_last = not reverse - right_expr = obj + # Then we determine whsat the value is for the obj we need to be after + value = obj for attr in field.split('__'): - right_expr = getattr(right_expr, attr) - if isinstance(right_expr, models.Model): - right_expr = right_expr.pk - right_expr = Value(right_expr) - - # To handle reverse we flip the expressions - if reverse: - left_exprs.append(right_expr) - right_exprs.append(left_expr) + value = getattr(value, attr) + if isinstance(value, models.Model): + value = value.pk + + # Now we make an expression for this field that can be: + # - True: if the field comes after the value in the ordering + # - False: if the field comes before the value in the ordering + # - None: if the field is the same as the value in the ordering (so we can coalesce on the next fields) + if value is None: + whens.append(When(Q(**{field + '__isnull': False}), then=Value(not nulls_last))) else: - left_exprs.append(left_expr) - right_exprs.append(right_expr) + whens.append(When(Q(**{field: None}), then=Value(nulls_last))) + whens.append(When(Q(**{field + '__lt': value}), then=Value(reverse))) + whens.append(When(Q(**{field + '__gt': value}), then=Value(not reverse))) - # Now we turn this into one big comparison - if len(ordering) == 1: - expr = GreaterThan(left_exprs[0], right_exprs[0]) - else: - expr = GreaterThan(Tuple(*left_exprs), Tuple(*right_exprs)) + expr = Case(*whens, default=Value(False)) return expr, required_annotations