Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Django ORM's `.first` return `None` for a non-empty queryset?

Tags:

python

django

I have a very simple model called Achievement. In an algorithm, I reach a point in which I have a queryset for this model - named achievements bellow. I got in a situation in which the method .first() applied to this queryset outputs None, even though there is an element in the queryset.

In summary,

achievements[0] # outputs an achievement
achievements.first() # None
achievements.count() # 1

How can this happen? There is no default ordering for this model.

like image 382
DMFerrer Avatar asked Oct 18 '25 07:10

DMFerrer


1 Answers

When one calls .first() on a queryset, the ordering of the queryset is important as that would decide what object is first. When the queryset has an order it is used, if it doesn't have one the primary key is automatically used for ordering. Quoting the documentation on first():

Returns the first object matched by the queryset, or None if there is no matching object. If the QuerySet has no ordering defined, then the queryset is automatically ordered by the primary key. This can affect aggregation results as described in Interaction with default ordering or order_by().

This automatic use of the primary key in the order, or let's say some other ordering that you provide will change how aggregates happen (As previously what may have been in a group will now not be in a group because of the order). Hence you observe what happens with your query.

Consider the following example (The Bar model is not needed but that is what I already had for testing purposes):

class Bar(models.Model):
    value = models.IntegerField()


class ForeignFoo(models.Model):
    bar = models.ForeignKey(Bar, on_delete=models.CASCADE)
    int_field = models.IntegerField(default=0)



# Queries start here
b1 = Bar.objects.create(value=1)
b2 = Bar.objects.create(value=2)

ForeignFoo.objects.create(bar=b1, int_field=1) # pk 1
ForeignFoo.objects.create(bar=b2, int_field=1) # pk 2
ForeignFoo.objects.create(bar=b1, int_field=1) # pk 3

queryset = ForeignFoo.objects.values('bar').annotate(total=Sum('int_field')).filter(total__gt=1)
print(queryset.first()) # None
print(queryset[0]) # Prints an object

Therefore instead of using .first you can simply do the follows to get the proper result:

result = None
sliced_queryset = queryset[:1]
if sliced_queryset:
    result = sliced_queryset[0]
print(result)
like image 112
Abdul Aziz Barkat Avatar answered Oct 20 '25 23:10

Abdul Aziz Barkat



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!