In a Django queryset, the double underscore isn't just a quirk of Python names — it's a small query language. field__lookup=value maps to a SQL operator:
| Lookup | SQL |
|---|---|
__exact (default) |
= |
__lt, __lte, __gt, __gte |
<, <=, >, >= |
__in |
IN (...) |
__isnull |
IS NULL / IS NOT NULL |
__icontains, __istartswith |
LIKE '%...%', case-insensitive |
__date, __year, __month |
DATE(col) = ..., etc. |
__regex, __iregex |
regex match |
The double underscore also crosses ForeignKey relations: Story.objects.filter(feed__user=request.user) joins through feed to filter on the related user.
This PR uses three lookups close together:
Q(next_check_at__isnull=True) | Q(next_check_at__lte=current) # IS NULL / <=
Story.objects.filter(feed__user=user, published_at__date=digest_date) # join + DATE()
Joining across a relation and truncating a date in one filter is what makes the ORM feel powerful — the SQL underneath is far from simple.
manage.py shell, and print str(qs.query) to see the generated SQL. Note which lookups produced which clauses.
Done when: you've matched at least three __lookups to their SQL counterparts in your own code.UserFeeds belonging to staff users (user__is_staff=True).
Done when: the queryset returns the right rows in shell.