-1

I'm fairly new to Django and was following a tutorial. A little ways through, I decided to make my own ordering for a model, but this involves a custom function.

This section of the program involves taking all the rooms and ordering them vertically on the page. Since the model is quite simple, I hope you can deduce the basic gist of the Room model as I am not very good at explaining (the entire model isn't shown but I think the relevant parts are). I want to order the rooms by the number of participants in the room. The function get_participants() defined at the bottom of the model does the work of getting the number of participants, however I cannot figure out how to apply this.

    class Room(models.Model):
        host = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
        name = models.CharField(max_length=200)
        participants = models.ManyToManyField(
            User, related_name='participants', blank=True)
    
        def get_participants(self):
            return len(self.participants.all())

Here is one of the view functions where I have attempted to implement this function for sorting. I read somewhere that you can use the built-in python sorted() method on QuerySets, though I'm not sure this is the case.

def home(request):
        q = request.GET.get('q') if request.GET.get('q') != None else ''
        rooms = sorted(Room.objects.filter(
            Q(topic__name__icontains=q) |
            Q(name__icontains=q) |
            Q(description__icontains=q) |
            Q(host__username__icontains=q)
        ), key=lambda room: room.get_participants())

It would be nice if I could implement the ordering into the model Meta, but if not any way of ordering using a custom function or achieving the desired result would be nice.

1 Answer 1

1

You can use sorted to order the results, but it is terribly inefficient because it means you have to loop through all the results after they have been fetched from the database. The proper way to do this is to apply the sorting in the database query itself. In order to do that, you first need to annotate your queryset with a count of rooms.

from django.db.models import Count
rooms = Room.objects.filter(
    Q(topic__name__icontains=q) |
    Q(name__icontains=q) |
    Q(description__icontains=q) |
    Q(host__username__icontains=q)
).annotate(
    num_participants=Count('participants')   # This will count the participants for each room
).order_by(
    '-num_participants'    # This will order the results in descending order of participant count
).distinct()

So you:

  1. Annotate the queryset to add a count to each Room of the number of participants.

  2. Use an order_by clause to order the results by this annotated value.

Note also that I've added a distinct() clause at the end - this is because the way you are filtering can result in duplicates being returned.

Sign up to request clarification or add additional context in comments.

2 Comments

This did exactly what I wanted it to; however, it ended up being a bit repetitive as there are multiple cases where I ended up calling this. Is there any way to make this the default ordering for the Room model instead of having to annotate over the QuerySet every time?
@AbeMonk Yes - you can use a custom manager on the Room model, where you modify the initial queryset. See docs.djangoproject.com/en/4.0/topics/db/managers/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.