Skip to content

Commit

Permalink
Fix after filter with null
Browse files Browse the repository at this point in the history
  • Loading branch information
daanvdk committed Nov 14, 2024
1 parent fc40658 commit 051e333
Showing 1 changed file with 27 additions and 23 deletions.
50 changes: 27 additions & 23 deletions binder/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1569,39 +1569,43 @@ 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
reverse = field.startswith('-')
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

Expand Down

0 comments on commit 051e333

Please sign in to comment.